· 6 min read

npm v12 Ships Next Week. Your CI/CD Pipeline Is Broken. Here's the Checklist.

npm v12 is shipping next week with a change that breaks 16 years of convention: install scripts are now blocked by default. Packages that rely on postinstall hooks, preinstall hooks, or prepare scripts will fail to run them unless you explicitly allow it with --scripts. This is a deliberate breaking change, and it's the right call. But it means your CI/CD pipeline is probably broken right now, and you don't know it yet.

Why npm broke 16 years of convention

For a decade, running npm install automatically granted every package in your dependency tree the ability to execute arbitrary shell commands. A postinstall hook can run anything: build a C extension, download a binary, phone home to a logging server, steal your SSH keys.

Attackers exploited this relentlessly in 2026:

  • Axios (March): North Korean state actor Sapphire Sleet hijacked the npm account of Axios's lead maintainer and published two malicious versions. The postinstall hook grabbed credentials.
  • node-ipc (May): Three malicious versions published simultaneously. Each carried an identical 80 KB obfuscated credential-stealing payload in the postinstall hook.
  • RedHat namespace (June): 32+ packages under @redhat-cloud-services were compromised. Malicious postinstall hooks, ~80K weekly downloads before removal.
  • Shai-Hulud worm (June): 796 packages, 132 million monthly downloads, postinstall hooks stealing credentials at scale.

That's not edge-case risk. That's the baseline for a popular package ecosystem with default arbitrary code execution.

What npm v12 actually changes

Starting this month, npm v12 blocks install scripts by default. Packages that have postinstall, preinstall, or prepare hooks will not run them. If a package legitimately needs to build a native extension or download platform-specific binaries, those scripts will be skipped.

You can override on a per-package basis:

npm install --scripts

Or you can whitelist specific packages in .npmrc:

allow-scripts=chai,esbuild,sharp

But the default is safe: no scripts run unless you tell npm to let them.

How this breaks your CI/CD

You have a production app with 200 transitive dependencies. You probably don't know which ones run install scripts. npm v12 is about to tell you.

Scenario: your CI/CD pipeline runs npm install. One of your transitive dependencies has a prepare hook that builds Wasm, TypeScript, or a native extension. That hook doesn't run. The dependency ships without its compiled output. Your build fails. You find out at 2am on a Friday.

Scenario 2: you run a monorepo with 40 packages. Three of them use prepare hooks to generate code before tests run. npm v12 skips the prepare. Tests fail. You debug for an hour before realizing the hook didn't run.

The audit checklist

Run this today, before npm v12 is the default:

npm install --verbose --loglevel=silly 2>&1 | grep -E "postinstall|preinstall|prepare|running scripts" > /tmp/npm-hooks.log

Read the log. Find every package that runs a script. For each one, ask:

  • Do we need this script? If it's a build step (native binding, Wasm compilation, code generation), you probably do.
  • Can we replace it? If it's downloading a platform-specific binary, can you use a prebuilt artifact instead? If it's code generation, can you check in the generated output?
  • Is it safe? If you don't know what the script does, read it. Check the GitHub repo. Check the maintainer's reputation.

Document the list in a comments block in your package.json or in your CI config:

{
  "_comment": {
    "npm-v12-scripts": [
      "sharp: postinstall builds native image library",
      "esbuild: prepare compiles binary for the platform",
      "grpc: preinstall builds gRPC stubs from .proto files"
    ]
  }
}

Then, in your CI/CD pipeline, explicitly allow scripts for those packages:

npm install --scripts-allowed=sharp,esbuild,grpc

Or, if you want to allow all scripts (not recommended, but sometimes necessary):

npm install --scripts

The honest take

Some packages genuinely need build scripts. Native bindings (sharp, node-sqlite3, node-gyp), compiled languages (esbuild, swc, Rust), and protocol buffers (grpc, protobuf) all legitimately need postinstall hooks.

But 80% of postinstall hooks are legacy. They could be replaced with prebuild artifacts, setup documentation, or build steps in your CI instead of in npm. They remain as install scripts because that's how they were first written, and nobody has bothered to change them.

npm v12 forces the question: do you actually need this script, or are you just letting it run because it's always run?

What to do this week

  1. Audit your dependencies today
  2. Document the list of scripts you actually need
  3. Test your CI/CD pipeline locally with npm v12 (install it: npm install -g npm@12)
  4. Update your .npmrc or CI config to whitelist the scripts you need
  5. Push before npm v12 becomes the default

If you skip this week, your builds will fail next week. npm won't break your code; it'll just skip the scripts. But if those scripts are part of your build, you'll see failures you don't understand.

Not to shame npm. This change is necessary. Attackers have been exploiting the default for years. But your team needs to prepare.

Author

Sources

Stay in the Loop

Get new posts delivered to your inbox. No spam, unsubscribe anytime.

Newsletter coming soon. Set PUBLIC_CONVERTKIT_FORM_ID in .env to activate.

Related Posts