Reduce the blast radius of npm install so 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.

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:

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:

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: