stout implements a defense-in-depth security model to protect against supply chain attacks, man-in-the-middle attacks, and compromised indexes.
┌─────────────────────────────────────────────────────────────────────────┐
│ stout Security Layers │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 1: Transport Security │
│ ├── HTTPS required (TLS 1.2+) │
│ └── Domain validation (trusted hosts only) │
│ │
│ Layer 2: Index Integrity │
│ ├── Ed25519 cryptographic signatures │
│ ├── Signature freshness validation (max age) │
│ └── Pinned public keys in binary │
│ │
│ Layer 3: Package Integrity │
│ ├── SHA256 checksums for all downloads │
│ └── Checksum verification before installation │
│ │
│ Layer 4: Installation Security │
│ ├── Sandboxed extraction │
│ └── No arbitrary code execution during install │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Every stout index is cryptographically signed using Ed25519 signatures. This ensures:
- Authenticity: The index was created by the official stout-index maintainers
- Integrity: The index has not been tampered with
- Freshness: The signature is recent (prevents replay attacks)
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Index DB │────▶│ SHA256 Hash │────▶│ Sign w/ │
│ (SQLite) │ │ │ │ Private Key │
└──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ stout │◀────│ Verify │◀────│ manifest │
│ client │ │ Signature │ │ .json │
└──────────────┘ └──────────────┘ └──────────────┘
- The index database is hashed (SHA256)
- The hash + metadata is signed with an Ed25519 private key
- The signature is stored in
manifest.json - stout verifies the signature using the embedded public key
{
"version": "2024.11.27.1234",
"index_version": "2024.11.27.1234",
"index_sha256": "abc123...",
"index_size": 3145728,
"formula_count": 7500,
"cask_count": 5000,
"signed_at": 1732723200,
"signature": "ed25519_signature_hex...",
"created_at": "2024-11-27T12:00:00Z"
}The signature covers a canonical string:
stout-index:v1:{index_sha256}:{signed_at}:{index_version}:{formula_count}:{cask_count}
stout security can be configured in ~/.stout/config.toml:
[security]
# Require valid Ed25519 signatures on index updates
# Default: true (release builds), false (debug builds)
require_signature = true
# Allow unsigned indexes (for development/testing only)
# Default: false (release builds), true (debug builds)
allow_unsigned = false
# Maximum age of signature in seconds before rejecting
# Default: 604800 (7 days)
max_signature_age = 604800
# Additional trusted public keys (for key rotation)
# The default stout-index key is always trusted
additional_trusted_keys = []stout supports three security modes:
| Mode | require_signature |
allow_unsigned |
Use Case |
|---|---|---|---|
| Strict | true |
false |
Production (default in release) |
| Default | varies | varies | Based on build type |
| Permissive | false |
true |
Development only |
The official stout-index public key is embedded in the binary:
e58d628836f72ecc7f6964ba2b70523d7c1c46512441ef8eccf2fa55ad0258f2
This key is used to verify all index updates. The corresponding private key is:
- Stored securely in GitHub Secrets
- Used only by the official CI/CD pipeline
- Never distributed or exposed
In strict mode, stout:
- Requires HTTPS for remote index URLs
- Enforces TLS 1.2 or higher
- Validates server certificates
- Allows
file://URLs for local mirrors
stout intentionally does not restrict which domains can host indexes. This is because:
- Signature verification is the primary security mechanism - a valid Ed25519 signature proves the data is authentic regardless of where it's hosted
- Enterprises need flexibility - mirrors can be hosted on internal domains, CDNs, or local file systems
- Defense in depth - HTTPS protects against network-level attacks, but the signature protects against compromised servers
# All of these are valid with proper signatures:
[index]
base_url = "https://raw.githubusercontent.com/neul-labs/stout-index/main" # Official
base_url = "https://stout-mirror.internal.company.com" # Enterprise
base_url = "https://cdn.example.com/stout" # CDN
base_url = "file:///opt/stout-mirror" # LocalAll downloaded bottles are verified:
- SHA256 Checksum: Verified against the formula's recorded hash
- Size Validation: Ensures the download is complete
- Content Verification: Validates the archive structure
// Verification happens before any extraction
if computed_hash != expected_hash {
return Err(Error::ChecksumMismatch);
}macOS applications (casks) are verified using:
- SHA256 checksums (when available)
- File size validation
- Code signature validation (macOS Gatekeeper)
stout includes built-in vulnerability auditing:
# Scan all installed packages
stout audit
# Scan specific packages
stout audit openssl curl
# Update vulnerability database
stout audit --updateThe vulnerability database is sourced from:
- OSV (Open Source Vulnerabilities)
- GitHub Security Advisories
- NVD (National Vulnerability Database)
Mirrors allow offline or internal hosting of stout indexes. The security model extends to mirrors:
┌─────────────────────────────────────────────────────────────────────────┐
│ Mirror Security Model │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Option 1: Upstream Signature Preservation │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Official │────▶│ Mirror │────▶│ stout │ │
│ │ Index │ │ (copies │ │ (verifies │ │
│ │ (signed) │ │ signature) │ │ signature) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ Option 2: Enterprise Re-signing │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Official │────▶│ Enterprise │────▶│ stout │ │
│ │ Index │ │ Mirror │ │ (verifies │ │
│ │ │ │ (re-signed) │ │ enterprise │ │
│ │ │ │ │ │ key) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
When creating a mirror, the upstream signature is preserved:
# Create mirror - signature is automatically copied
stout mirror create /path/to/mirror jq curl git
# Mirror manifest includes:
# - upstream_signature.signature (original Ed25519 signature)
# - upstream_signature.index_sha256 (original hash)
# - upstream_signature.signed_at (original timestamp)The stout client verifies the upstream signature even when fetching from a mirror. This ensures:
- The data originally came from the official index
- No tampering occurred during mirroring
- The signature age is still validated
Enterprises can sign mirrors with their own keys:
# Generate enterprise keypair
cd /path/to/stout-index/scripts
uv run python sign_index.py generate --output ./enterprise-keys
# Create and sign mirror
stout mirror create /path/to/mirror jq curl git
uv run python sign_index.py sign \
--key ./enterprise-keys/stout-index.key \
--index-dir /path/to/mirrorConfigure clients to trust the enterprise key:
# ~/.stout/config.toml
[security]
additional_trusted_keys = [
"YOUR_ENTERPRISE_PUBLIC_KEY_HEX"
]| Scenario | Upstream Sig | Mirror Sig | Security Level |
|---|---|---|---|
| Official mirror | ✓ Preserved | Optional | Full (official trust) |
| Enterprise mirror | ✓ Preserved | ✓ Added | Full (dual trust) |
| Custom curated | ✗ N/A | ✓ Required | Enterprise trust only |
| Development | ✗ N/A | ✗ None | Permissive mode only |
For air-gapped environments:
- Create mirror on connected system: Signature is preserved
- Transfer to air-gapped system: Via approved media
- stout verifies signature: Using embedded public key
- No network required: Verification is fully offline
# On air-gapped system
stout --prefix=/opt/airgap update # Uses local mirror
# ✓ Signature verified (signed 2h ago)For enterprise deployments, see ENTERPRISE.md for:
- Private index hosting
- Custom signing keys
- Air-gapped installations
- Audit logging
- Compliance considerations
- Keep stout updated: Security fixes are released regularly
- Run
stout audit: Regularly scan for vulnerable packages - Verify the installer: Check checksums when downloading stout
- Don't disable security: Avoid
--insecureflags in production
- Host your own index: Full control over package sources
- Use your own signing key: Independent verification chain
- Enable audit logging: Track all package operations
- Regular vulnerability scans: Automate
stout auditin CI/CD
If you discover a security vulnerability in stout:
- Do not open a public GitHub issue
- Email security concerns to the maintainers privately
- Include:
- Description of the vulnerability
- Steps to reproduce
- Potential impact
- Suggested fix (if any)
We aim to respond within 48 hours and will coordinate disclosure.
- Algorithm: Ed25519 (RFC 8032)
- Key size: 256-bit (32 bytes)
- Signature size: 512-bit (64 bytes)
- Hash function: SHA-512 (internal to Ed25519)
- Fast: Verification is extremely fast
- Small: Compact keys and signatures
- Secure: No known practical attacks
- Deterministic: Same input always produces same signature
- Widely supported: Available in all major crypto libraries
| Feature | stout | Homebrew |
|---|---|---|
| Index signatures | Ed25519 | None (git commit hashes) |
| Transport security | HTTPS required | HTTPS (via git) |
| Bottle checksums | SHA256 | SHA256 |
| Code signing (macOS) | Gatekeeper | Gatekeeper |
| Vulnerability scanning | Built-in | External tools |
| Offline verification | Yes | No (requires git) |
| Version | Changes |
|---|---|
| 0.1.0 | Initial security model with Ed25519 signatures |
For questions about stout security, see the FAQ or open a discussion on GitHub.