npm Supply Chain Security: Install Scripts, Release Cooldowns, Provenance, and Trusted Publishing
Compromised maintainer accounts and self-propagating worms made npm installs an attack surface. The threat model, disabling install scripts, release cooldowns in npm, pnpm, Yarn, and Bun, lockfile discipline, provenance, trusted publishing, and isolating installs.
An average JavaScript app pulls in hundreds to thousands of transitive packages, maintained by people you've never heard of, any of whom can publish a new version at any time. Recent years made the risk concrete: phished maintainer accounts publishing malicious versions of widely used packages, and in 2025 a self-propagating worm that stole npm tokens from infected developer machines and CI to publish itself into further packages. The registry and package managers responded with real defences. This is how to use them. (For basics, see npm and package.json Explained and How to Update Dependencies Safely.)
Threat model
- Account takeover — a maintainer's credentials or tokens are stolen; a malicious patch release goes out under a trusted name.
- Malicious install scripts —
preinstall/install/postinstallrun arbitrary code onnpm install, with access to your environment variables, SSH keys, cloud credentials, and tokens. - Typosquatting and slopsquatting — look-alike names, or names AI tools hallucinate. (AI Hallucinated a Package.)
- Dependency confusion — a public package shadows an internal package name.
- Protestware / maintainer sabotage — rarer, but real.
- Compromised build pipelines — the published tarball doesn't match the source repository.
Most malicious versions are detected and removed within hours to days. That observation drives the single most effective consumer-side control.
1. Release cooldowns (minimum release age)
Refuse to install versions younger than a threshold. A malicious release usually gets caught and yanked before the window elapses.
| Manager | Setting | Notes |
|---|---|---|
| npm | min-release-age in .npmrc |
Recent npm 11 releases; value in days |
| pnpm | minimumReleaseAge |
Added in 10.16; pnpm 11 enables it by default (1 day); value in minutes |
| Yarn (Berry) | npmMinimalAgeGate in .yarnrc.yml |
Value in minutes |
| Bun | minimumReleaseAge in bunfig.toml [install] |
Value in seconds |
Check your version's documentation for exact units and exclusion lists — you'll want to exempt your own organisation's packages and allow emergency security patches deliberately.
2. Don't run dependency install scripts by default
- pnpm 10+ doesn't run dependencies' lifecycle scripts unless explicitly allowed (an allowlist, managed with
pnpm approve-builds; pnpm 11 consolidates this underallowBuilds). - Yarn Berry can disable scripts globally (
enableScripts: false) — the default in recent 4.x releases. - Bun runs scripts only for
trustedDependencies. - npm:
ignore-scripts=truein.npmrc, then run the few builds you need explicitly; newer npm versions add finer-grained allow controls.
Most packages don't need install scripts. The ones that do (native modules, some binary downloaders) should be an explicit, reviewed list.
3. Lockfile discipline
- Commit the lockfile; install with
npm ci/pnpm install --frozen-lockfile/yarn install --immutablein CI and production so nothing resolves fresh. (Semantic Versioning Explained.) - Review lockfile diffs in pull requests — new packages, changed
resolvedURLs (pointing somewhere other than the registry), changed integrity hashes on unchanged versions. Tools can summarise lockfile changes in PR comments. - Batch dependency updates through a bot (Renovate or Dependabot) with the same cooldown, grouped and tested, rather than ad-hoc
npm update.
4. Verify provenance and signatures
- Registry signatures:
npm audit signaturesverifies packages in your tree were signed by the registry and reports provenance attestations where present. - Provenance (Sigstore-based) links a published version to the source repository, commit, and CI workflow that built it. A popular package that suddenly publishes a version without provenance, when previous versions had it, is a red flag worth alerting on.
- Provenance proves where a package was built, not that the code is benign. It raises the bar for account-takeover attacks that publish from a laptop.
5. For maintainers: trusted publishing
If you publish packages, remove long-lived tokens from the equation:
- Trusted publishing (OIDC) lets GitHub Actions and GitLab CI/CD publish using short-lived, workflow-scoped credentials — no npm token stored anywhere. Provenance is generated automatically.
- npm revoked classic tokens in late 2025. Granular write tokens now have short default lifetimes and a capped maximum, and
npm loginissues short-lived session tokens. - Require 2FA for publishing and account changes (prefer security keys/passkeys over TOTP — phishing kits target TOTP codes).
- Protect the publishing workflow: environment protection rules, pinned action SHAs, minimal
permissions. (GitHub Actions CI Basics.)
6. Scope and naming
- Publish internal packages under an organisation scope (
@yourco/…) and configure the scope's registry explicitly, so a public package can't shadow it (dependency confusion). - Reserve your scope on the public registry even if you only publish privately.
7. Isolate installs and builds
Assume some install will eventually run hostile code, and limit what it can reach:
- No long-lived secrets in the install environment. CI jobs that install dependencies shouldn't have publish tokens or production cloud credentials; split jobs by privilege. (Secrets Management Beyond .env Files.)
- Developer machines: run installs and AI coding agents in containers or remote workspaces that don't contain
~/.ssh,~/.aws, or browser profiles. (How to Run AI-Generated Code Safely.) - Restrict egress during installs to the registry (or an internal mirror/proxy), so exfiltration fails. (Agent Egress Control.)
- An internal registry proxy (Verdaccio, Artifactory, or a hosted equivalent) gives you one place to enforce cooldowns, block known-bad versions, and keep builds working when the public registry has issues.
8. Detect and respond
- Subscribe to advisories for your dependencies; run a malware-aware scanner in CI (several vendors flag suspicious install scripts, obfuscation, and network calls in new versions).
- Keep an SBOM (e.g. CycloneDX) per build so "are we affected?" is a query, not an archaeology project.
- If you installed a malicious version: treat the machine or runner as compromised — rotate every credential it could read, check for persistence (modified shell profiles, new SSH keys, unexpected GitHub workflows in your repos), and audit recent publishes from your accounts. (I Leaked an API Key. What Now?.)
AI coding agents raise the stakes
Agents add dependencies quickly and can run installs without a human reading the package name. Require approval for install commands, give agents the same cooldown and no-scripts configuration via committed config files, and have them justify each new dependency in the PR description. (Claude Code Permission Modes.)
Checklist
- Minimum release age configured for your package manager
- Dependency lifecycle scripts disabled except an explicit allowlist
- Frozen-lockfile installs in CI; lockfile diffs reviewed
-
npm audit signaturesin CI; alert on provenance regressions - Maintainers: trusted publishing, no stored tokens, phishing-resistant 2FA
- Internal packages scoped; registry configured per scope
- Installs run without secrets and with restricted egress
- SBOM per build; a rehearsed credential-rotation plan
EasySpawn runs each project — and every npm install Claude Code triggers — in its own isolated Docker workspace, with no access to your laptop's credentials or other projects, so a bad package's blast radius is one sandbox. See how it works or join the waitlist.
Related: AI Hallucinated a Package · Container Hardening: Seccomp and AppArmor · Prompt Injection and Coding Agents
Keep reading
Egress Control for AI Agents: Designing an Allowlist Proxy
Restricting where an agent can send data is the most reliable defence against exfiltration, and easy to get subtly wrong. Network-layer enforcement, SNI vs TLS interception, DNS as a covert channel, allowlisted domains as leak paths, credential brokering, and testing.
Where Should User Uploads Go? Object Storage Explained
Profile photos that vanish after a deploy, a database bloated with images, a public bucket full of private documents. Where uploaded files should live, how object storage works, and the three decisions — public or private, who uploads, and how files are served — that keep uploads fast and safe.