Release age gates, install-script blocking and integrity checks for 14 package managers,
so a careless npm install or pip install is hardened without the caller knowing.
For servers, AI-agent hosts and CI pipelines. Then a verifier asks every tool what it actually enforces.
An AI agent, a CI job or a teammate reaches for whichever package manager is closest, and the newest
version is the one that runs. This project moves the safe choice one level down, into the package
managers themselves, so the protection is already there before anyone types install.
Freshly published versions are the ones that get pulled after a compromise. A default cooldown means you are never the first to install them.
Lifecycle hooks are the classic execution path for malicious packages. They are off by default across npm, pnpm, Yarn, Bun, Deno, pip/uv, Composer and NuGet.
Config files are written to their expected paths whether or not the package manager exists yet. Install pnpm next month and the hardened config is already waiting.
Coverage isn't uniform. Each ecosystem exposes a different set of knobs, and the role applies the strictest one available rather than pretending the gaps aren't there.
| Protection | npm | pnpm | Yarn | Bun | Deno | pip/uv | Cargo | Go | Composer | Bundler | Maven | Gradle | NuGet |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 48h release age gate | * | — | — | — | — | — | — | ||||||
| Install script blocking | — | — | — | — | — | ||||||||
| Pre-install reputation (npq) | — | — | — | — | — | — | — | — | — | — | |||
| Socket Firewall | — | — | — | — | — | — | — | — | — | — | |||
| Exact version pinning | — | — | — | — | — | — | — | — | — | — | |||
| Hash / integrity verification | — | — | — | — | — | — | — | — | |||||
| HTTPS-only / source pinning | — | — | — | — | — | — | — | ||||||
| Lockfile enforcement | — | — | — | — | — | — | — | — | — | — | — |
cargo-cooldown crate, through the cargo PATH wrapper
Every protection is delivered in up to three layers: system-wide env vars, config files and PATH wrappers. How it works →
Every real failure in this project's history had the same shape: the file was exactly right and the tool quietly ignored it. So the role ships a verifier that asks the tools what they ended up believing, on the real host, against the real installed versions. It runs after every apply and at the end of every CI job.
STATUS EVIDENCE PROTECTION DETAIL OK PARSED npm lifecycle scripts blocked npm reports ignore-scripts=true GAP PARSED yarn age gate yarn reports non-integer npmMinimalAgeGate='NaN' GAP FUNCTIONAL npq reputation checks installed but SUPPRESSED on Node v18.19.1 WEAK PRESENT npm PATH wrapper wrapper installed; callers bypassing PATH unaffected
The role configures the package managers that are already there; it doesn't install them. Apply it to a bare host, a sandbox or a container image, or drop the Action into a pipeline.
# install the role, apply to this host, check it
ansible-galaxy role install \
echennells.supply_chain_hardening
ansible-playbook site.yml --limit localhost
supply-chain-verify
- uses: actions/setup-node@v4 # toolchains first - uses: echennells/supply-chain-hardening/action@v2 - run: npm ci # protected from here - uses: echennells/supply-chain-hardening/action/verify@v2
# Dockerfile, GitLab, CircleCI, Azure, Buildkite
COPY action/harden.sh /usr/local/lib/
RUN ECOSYSTEMS=npm,pnpm,pip,uv \
/usr/local/lib/harden.sh --emit=plain
A sandbox controls what can run. This controls how package managers behave when they do. The two are complementary, and the boundaries are documented rather than papered over.
Env vars and config files protect against careless installs, not a determined attacker who already has code execution.
apt, pacman and the AUR have their own trust mechanisms. Judging this against distro-package risk measures it against a threat it never claimed.
The layers overlap on purpose, so the file you'd grep is often not the one doing the work. supply-chain-verify is the source of truth.
Every known boundary, each one measured on a real host: docs/limitations.md →
MIT licensed. Exercised in CI on Ubuntu 22.04, 24.04 and 26.04, and Debian 12.