npm hardening
Reduce the blast radius of
npm installso that a compromised package can't trivially own you.
Canonical playbooks (read first)
If you take the time to read one thing, read these. The rest of this doc summarizes and adapts their advice for vibe coders.
- OWASP NPM Security Cheat Sheet — the foundational reference.
- Snyk — NPM Security Best Practices After the Shai-Hulud Attack — updated specifically for the 2025–2026 worm wave.
- npm Docs — Trusted publishing for npm packages — official, kill long-lived publish tokens.
- Lirantal — npm-security-best-practices —
npqauthor, Node.js Security Working Group lead. - bodadotsh — npm-security-best-practices — continuously updated post-incident.
- Mondoo — npm Supply Chain Security in 2026 — what
npmdoes and does not protect you from. - Lessons from the Spring 2026 OSS Incidents — npm + pnpm + GitHub Actions hardening, post-TanStack.
The single highest-leverage setting
npm config set ignore-scripts true
Globally disables preinstall / install / postinstall scripts. Shai-Hulud, Nx s1ngularity, and most npm worms execute via postinstall — this neuters the most common attack vector with one line. (Per Snyk and Mondoo, this is the #1 recommended setting.)
Trade-off: a few legitimate packages need install scripts (esbuild, sharp, node-sass). For those, audit what they do, then enable per-install:
npm install <pkg> --foreground-scripts --ignore-scripts=false
Or maintain a per-project .npmrc re-enabling scripts only in projects you've vetted.
Use npm ci, not npm install, in CI
npm install resolves versions live and can pull a freshly-published malicious version. npm ci installs exactly what's in package-lock.json — same SHAs, every time. (Snyk, Mondoo.)
# In every CI job
npm ci --ignore-scripts
Add a release cooldown (most underused defense)
Malicious packages are usually discovered and unpublished within hours or days. Skipping any version published in the last 3 days gives the community time to catch problems before they reach you — see Mondoo's writeup.
# npm
npm config set before "$(date -u -d '3 days ago' +%Y-%m-%dT%H:%M:%SZ)"
# pnpm has a built-in minimumReleaseAge setting
echo 'minimum-release-age=4320' >> .npmrc # 4320 minutes = 3 days
For organizations: enforce the cooldown at a registry proxy (Cloudsmith, JFrog Xray, Sonatype Nexus Firewall). New tarballs are held for 24–72h before sync, blocking install during the typical detection-and-takedown window.
Pin exactly, not loosely
Avoid ^ and ~ for security-relevant deps (build tools, HTTP clients, IPC libs). Pin exact:
{
"dependencies": {
"axios": "1.7.4", // not "^1.7.4"
"node-ipc": "9.2.1" // not "~9.2.1"
}
}
The lockfile already pins SHAs, but a fresh npm install (vs npm ci) can resolve a new in-range version and replace the lock entry — including the malicious one if you're unlucky.
Use overrides to block known-bad versions
When an advisory hits, block the bad version range across the entire transitive tree:
{
"overrides": {
"axios": "1.7.4",
"node-ipc": "9.2.1"
}
}
For yarn use resolutions. For pnpm use pnpm.overrides.
Validate lockfile integrity
lockfile-lint validates that package sources come from trusted registries and no malicious modifications were introduced. Run in pre-commit:
npx lockfile-lint --path package-lock.json \
--allowed-hosts npm \
--validate-https \
--validate-package-names \
--validate-integrity
Scan before merge
Wire these into pre-commit and CI:
- Socket — reputation per package + supply-chain risk indicators. Free for open source.
- OSV-Scanner — Google's all-ecosystem scanner backed by osv.dev.
- Snyk Open Source — known CVEs in deps.
npq— wrapsnpm installand prompts before installing low-reputation packages.
About
npm audit: useful but limited. It checks known advisories from GHSA; it can't detect novel typosquats, account takeovers, or freshly-injected malware. Don't rely on it alone — see Why npm Audit Is Broken.
If you publish packages: enable trusted publishing
Stop storing long-lived npm tokens in CI. Trusted publishing (npm CLI ≥11.5.1) lets your GitHub Actions or GitLab CI workflow publish via OIDC — no token in secrets, ever. GA'd July 2025; bulk config GA'd Feb 2026. The phished qix token and Nx s1ngularity token would both have been useless with trusted publishing.
Pair with npm provenance so consumers can verify the build pipeline produced the artifact:
npm publish --provenance
Note the TanStack Mini Shai-Hulud advisory: valid SLSA provenance proves which pipeline built the artifact, not that the pipeline was uncompromised. Provenance is necessary but not sufficient — combine with workflow hardening below.
Audit the lockfile diff in every PR
Code review: when a PR touches package-lock.json / yarn.lock / pnpm-lock.yaml, read the new entries. A PR titled "fix typo" that adds 47 transitive deps is a flag.
For consumers downstream of CI/CD
Full guide: ci-cd-hardening.md. Harden your GitHub Actions workflows — most modern npm worms (Shai-Hulud 2.0, TanStack Mini) propagate via compromised CI:
- GitHub's official Secure use reference
- Wiz — Hardening GitHub Actions: Lessons from Recent Attacks
zizmor— static analysis for Actions workflows; catches the Pwn Request pattern that hit TanStack- StepSecurity Harden-Runner — runtime egress allowlist for Actions
- Pin actions to commit SHAs, not version tags. (
uses: foo/bar@abc1234…notuses: foo/bar@v1.)
What this stack stops
| Attack | Mitigation |
|---|---|
| Shai-Hulud, Nx s1ngularity (postinstall) | ignore-scripts |
| Axios, qix (auto-update to bad version) | npm ci + exact pins + cooldown |
| Brand-new malicious package | cooldown + Socket pre-install check |
| Transitive bad versions | overrides / resolutions |
| Maintainer phishing → publish-as-you | trusted publishing + hardware 2FA |
| TanStack-style CI-pipeline compromise | hardened Actions + cooldown + Socket alerts |
| Lockfile tampering | lockfile-lint |
What it doesn't stop
- A legitimately-trusted maintainer's account compromised AND your scanner not yet flagging it AND your cooldown not yet elapsed. This is the irreducible residual risk — the only real mitigation is distance (vendor the dep, fork it, or don't depend on it).
- Provenance-signed but pipeline-compromised packages (the TanStack pattern). Defense-in-depth still helps: registry proxy hold-window + Socket alerts catch most of these within hours.
Reference frameworks
For teams formalizing this:
- SLSA — Supply-chain Levels for Software Artifacts (OpenSSF project) — incrementally adoptable supply-chain integrity levels. npm has the deepest SLSA integration of any package ecosystem.
- NIST SSDF (SP 800-218) — Secure Software Development Framework.
- CISA — Defending Against Software Supply Chain Attacks.
- OpenSSF Scorecard + Allstar — auto-score and enforce baseline security policies on your repos.