Guide · Supply-chain security · as of 29 October 2026

Hardening an npm CI/CD pipeline

A package firewall is the enterprise answer. Most of what stopped the 2025 and 2026 attacks, though, is free and already in your package manager. Here are the controls, roughly in the order they pay off, each one a setting you can turn on today.

1

Turn install scripts off

The payload runs at install, not at import.

Almost every npm attack fires from a package’s install script, which runs arbitrary code the moment the package lands. Set ignore-scripts=true in your .npmrc, or run npm install --ignore-scripts, and that door is shut. A few legitimate packages need their build step, so allow those by name rather than leaving the setting off for everything.

2

Wait before you upgrade

Most poisoned versions are caught within days.

A malicious release is usually detected and pulled within hours or a day or two. If you simply do not install a version until it has aged, you miss most of them. pnpm has this natively: set minimumReleaseAge in its config to hold every new version back a few days. GitHub’s package firewall offers the same thing as a policy cooldown. The enterprise version is the firewall; the free version is patience.

GitHub’s npm package firewall

3

Install what you pinned

npm ci, never npm install, in CI.

In your pipeline, run npm ci, not npm install. It installs exactly the versions in your lock file and fails if the lock file and package.json disagree, so a build can never silently drift onto a newer, unreviewed release. Commit the lock file, review changes to it in pull requests the way you review code, and keep the resolved URLs in it pointing at the registry you expect.

4

Starve the token

Least privilege, short lived.

Set an explicit permissions block in every GitHub Actions workflow, default it to contents: read, and grant more only to the job that needs it. Publish with OIDC trusted publishing instead of a long-lived npm token, so there is no standing secret to steal. (Note that attackers have abused trusted publishing too, so pair it with a cooldown and the firewall.) If a secret is ever exposed, rotate it immediately and scan the whole git history, not just the latest commit, because the October 2026 campaign read history for old credentials.

5

Sign and verify

Prove where a package came from.

When you publish your own packages, publish with provenance (npm publish --provenance) so consumers can verify the build they came from. When you install, prefer packages that ship signed provenance, and treat a package that used to be signed and suddenly is not as a red flag. That drop is exactly the attestation downgrade GitHub’s firewall watches for.

6

Fence the runner

A build machine should not reach the world.

Restrict what a CI runner can reach on the network to the registry and the services it truly needs. If a poisoned package does run, it has nowhere to send what it steals, and it cannot route around a firewall it is forced to go through.

The shape of it

Assume every dependency is hostile.

  • Delay: age a version before you trust it.
  • Disarm: no install scripts, pinned by lock file.
  • Starve: short-lived, least-privilege tokens.
  • Check: route installs through a firewall with a policy.

Sources

A question?

Want another pair of eyes on your pipeline?

Tell us how you build and ship, and we will tell you honestly where a dependency could get in.

Talk to usWhatsApp +971 50 728 5494hello@brevdigital.com