Defending against supply-chain attacks

npm · Expert · 15 min

Malicious package versions and install scripts are the most common way attackers reach developer machines and CI.

Real attacks on the npm ecosystem follow a few patterns: typosquatting (expresss), compromised maintainer accounts publishing malicious versions of popular packages, dependency confusion (a public package with the name of your private one), and install scripts that steal tokens the moment you run npm install.

Defences built into npm:

  • Install script policy. The allowScripts field in package.json lists which dependencies may run preinstall/install/postinstall. Manage it with npm approve-scripts and npm deny-scripts. In npm 11 it is advisory (installs report unreviewed scripts); npm 12 blocks unreviewed dependency install scripts by default. --ignore-scripts disables them entirely.
  • Git and URL dependencies: allow-git and allow-remote limit them; npm 12 defaults both to none.
  • Cooling-off period: min-release-age=3 (days) avoids versions published minutes ago, the window in which most malicious releases are caught and removed.
  • Scoped private packages routed to your registry prevent dependency confusion.
  • npm ci with a reviewed lockfile, npm audit signatures, and provenance checks.

Around npm: run CI installs with no secrets in the environment, give publish jobs separate minimal permissions, use trusted publishing, and consider package firewalls or scanners (Socket, Snyk, GitHub dependency review) that flag suspicious new versions.

Example

{
  "name": "shop-api",
  "private": true,
  "allowScripts": {
    "esbuild@0.25.10": true,
    "sharp": true,
    "some-telemetry-lib": false
  }
}

// Shell:
// npm approve-scripts --allow-scripts-pending   # list unreviewed install scripts
// npm approve-scripts esbuild                   # pin approval to installed version
// npm deny-scripts some-telemetry-lib           # never run its scripts
//
// .npmrc:
//   min-release-age=3
//   allow-git=none
//   allow-remote=none

Remember

  • Attacks: typosquats, hijacked maintainers, dependency confusion, install scripts
  • allowScripts + approve/deny-scripts control install scripts (enforced in npm 12)
  • Restrict git/URL deps and wait out fresh releases with min-release-age
  • Keep secrets out of CI install steps

Watch out for

  • Running npm install in CI jobs that also hold deploy credentials in environment variables.

Check yourself

  1. Why does a `min-release-age` of a few days reduce risk?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.npmjs.com/cli/v11/using-npm/config#min-release-age