Package Manager Security Best Practices
Supply chain attacks target package managers. Here's how to protect your development environment — signed packages, vulnerability scanning, and verified indexes.
Supply chain attacks against package managers are no longer theoretical. The SolarWinds compromise, the ua-parser-js npm hijack, the codecov bash uploader breach, the xz-utils backdoor — each demonstrated that attackers are systematically targeting the software distribution pipeline. If you can compromise a package manager, you can compromise every machine that installs from it.
System-level package managers like Homebrew are particularly high-value targets. They run with elevated privileges, install binaries that other software depends on, and are used by millions of developers. Yet most developers give almost no thought to the security of their package management setup.
Here are concrete best practices for securing your package management workflow, with specific attention to the threat models that matter in 2026.
Threat model: What are you defending against?
Before discussing mitigations, it helps to be specific about the threats:
Compromised package registry. An attacker gains access to the registry infrastructure and replaces a legitimate package with a malicious one. This happened with the ua-parser-js npm package in 2021.
Compromised maintainer account. An attacker gains access to a package maintainer’s account (phishing, credential stuffing, social engineering) and publishes a malicious update. The event-stream npm attack in 2018 used a variant of this approach.
Dependency confusion. An attacker publishes a public package with the same name as an internal package, and the resolver picks the public (malicious) version. This affected Apple, Microsoft, and others in 2021.
Man-in-the-middle. An attacker intercepts package downloads and substitutes modified binaries. This is especially relevant in air-gapped environments where packages are transferred via physical media.
Compromised build infrastructure. An attacker compromises the build farm that produces pre-built binaries. The xz-utils backdoor in 2024 was a sophisticated example where the attacker spent years building trust before injecting malicious code into the build process.
Best practice 1: Verify package signatures
Package signing is the single most important defense against supply chain attacks. A cryptographic signature proves that a package was built by the expected party and hasn’t been modified since.
Homebrew’s approach: Homebrew does not sign individual bottles (pre-built binaries). Bottles are served over HTTPS from ghcr.io (GitHub Container Registry), which provides transport encryption. But HTTPS only verifies that you’re talking to the real server — it doesn’t verify that the binary on the server hasn’t been tampered with. If ghcr.io is compromised, or if a bottle is corrupted during the build process, HTTPS won’t save you.
Homebrew does provide SHA-256 checksums in its formulae, but the formulae themselves are distributed via a git repository without commit signing enforcement. The chain of trust has gaps.
stout’s approach: Every package in stout’s registry is signed with Ed25519. Ed25519 was chosen over RSA or GPG for several reasons:
- Small signatures (64 bytes) that add negligible overhead to package metadata
- Fast verification — thousands of signatures per second on any modern CPU
- No configuration complexity — unlike GPG, there’s no web of trust to manage, no keyring to maintain, no expired subkeys to deal with
- Deterministic — the same input always produces the same signature, which simplifies testing and auditing
When stout installs a package, it verifies the Ed25519 signature before extraction. If the signature doesn’t match, the install is rejected:
$ stout install ffmpeg
Fetching [email protected]...
Signature verification FAILED
Expected signer: registry.stout.dev (key: Sd8x...)
Package may have been tampered with.
Aborting installation.
For organizations using private registries, stout supports custom signing keys. Your platform team generates a keypair, signs packages with the private key, and distributes the public key to developer machines via configuration management.
Best practice 2: Pin and lock dependencies
Unpinned dependencies are a security risk because they allow an attacker to inject malicious code via a version update. If your CI runs brew install openssl and the latest version of openssl in homebrew-core has been tampered with, your CI is compromised.
Lock files mitigate this by pinning exact versions and checksums:
# Generate a lock file with stout
stout lock > stout.lock
# Install from the lock file — rejects anything that doesn't match
stout install --lockfile stout.lock
With a lock file, even if a package in the registry is replaced with a malicious version, the checksum mismatch will prevent installation. You’ve decoupled “what gets installed” from “what’s currently in the registry.”
Review lock file changes carefully in code review, just as you would review changes to package-lock.json. An unexpected version bump or checksum change could indicate a supply chain compromise.
Best practice 3: Use vulnerability scanning
Knowing that you have a vulnerable package installed is the prerequisite for patching it. Most language-level package managers have integrated vulnerability databases (npm audit, cargo audit, pip-audit). System-level package managers generally don’t.
stout includes a built-in vulnerability check:
$ stout audit
Checking 47 installed packages against vulnerability database...
VULNERABILITY FOUND
Package: libxml2 2.12.3
CVE: CVE-2026-1234
Severity: HIGH
Fixed in: 2.12.4
Recommendation: run `stout update libxml2`
VULNERABILITY FOUND
Package: curl 8.5.0
CVE: CVE-2026-5678
Severity: MEDIUM
Fixed in: 8.6.0
Recommendation: run `stout update curl`
2 vulnerabilities found (1 high, 1 medium)
Run stout audit in CI to fail builds that use packages with known vulnerabilities. Run it on developer machines as a pre-commit check or periodic scan.
Best practice 4: Minimize your attack surface
Every installed package is a potential attack vector. The more packages you have, the larger your exposure to future vulnerabilities.
Audit what’s installed and remove what you don’t need:
# List all installed packages
stout list
# Show why a package is installed (dependency of what?)
stout why libpng
# Remove a package and its orphaned dependencies
stout uninstall imagemagick --prune
In CI, start from a minimal base and install only what the build requires. Avoid convenience packages that pull in large dependency trees. If you need jq in CI, install jq — don’t install a package that happens to include jq as a transitive dependency alongside 40 other libraries.
Best practice 5: Restrict package sources
By default, package managers pull from public registries that anyone can publish to. In an enterprise environment, you should control which sources are trusted.
stout supports registry allowlists:
# stout.toml
[security]
allowed_registries = [
"https://registry.stout.dev",
"https://internal-registry.corp.example.com"
]
# Reject packages not from an allowed registry
strict_registry = true
This prevents dependency confusion attacks. If an attacker publishes a package with the same name as your internal tool on the public registry, stout will refuse to install it because the public registry isn’t in the allowlist (or the internal registry takes precedence).
Best practice 6: Audit the installation process itself
The package manager binary is the most privileged component in the chain. If the package manager itself is compromised, all other security measures are moot.
Verify the stout binary after download:
# Download and verify in one step
curl -fsSL https://get.stout.dev | sh
# Or download and verify manually
curl -fsSL -o stout https://get.stout.dev/binary/macos-arm64
curl -fsSL -o stout.sig https://get.stout.dev/binary/macos-arm64.sig
# Verify with a known public key
openssl pkeyutl -verify -pubin -inkey stout-signing-key.pub \
-sigfile stout.sig -in stout
stout is a single static binary with no runtime dependencies. There’s no Ruby runtime, no Python interpreter, no Node.js — just one binary. This dramatically reduces the attack surface compared to package managers that depend on complex runtime environments. A compromised Ruby gem, for instance, could affect Homebrew without anyone modifying Homebrew itself.
Best practice 7: Enable audit logging
In regulated environments, you need to know who installed what, when, and from where. stout logs every operation:
$ stout log --since 7d
2026-04-01 10:30:15 install [email protected] registry.stout.dev user=deploy
2026-04-01 10:30:15 install libx264@164 registry.stout.dev user=deploy
2026-04-02 14:22:01 update [email protected]->8.6.0 registry.stout.dev user=admin
2026-04-03 09:15:30 uninstall [email protected] — user=deploy
Forward these logs to your SIEM for correlation with other security events. An unexpected stout install at 3am from a service account that normally doesn’t install packages could be an indicator of compromise.
The compounding effect
No single practice is sufficient. Supply chain security works through defense in depth:
- Signed packages prevent tampering after build
- Lock files prevent unexpected version changes
- Vulnerability scanning catches known-bad versions
- Minimal installs reduce exposure
- Registry restrictions prevent confusion attacks
- Binary verification protects the tool itself
- Audit logging enables detection and response
Homebrew provides HTTPS transport and SHA-256 checksums in formulae. stout provides all seven layers. For organizations where supply chain security is a requirement — and in 2026, that should be every organization — the tooling matters.
Need Rust performance engineering or AI agent expertise?
Neul Labs — the team behind stout — consults on Rust development, performance optimization, CLI tool design, and AI agent infrastructure. We build fast, reliable systems that ship.