Short version: the npm 12 install-script lockdown does not fail your build. It skips what it does not recognise, exits 0, and leaves you a node_modules tree that is structurally complete and functionally hollow. Across one repo that is an afternoon. Across an estate it is a rollout where the pipeline stays green the whole way and the failures surface later, one service at a time, in production. The sequence below puts every failure in front of a human before it reaches a request.
npm 12.0.0 shipped on 8 July 2026 and flipped allow-git and allow-remote from all to none, with install scripts off unless a package is listed in allowScripts. GitHub pre-announced the change on 9 June. The allowScripts field itself landed earlier, in npm 11.16, so there is a window where the same repo warns on one runner and enforces on another depending on which CLI the image happens to carry.
For a single project this is a solved problem, and the walkthrough for it is the allowScripts warning fix. This piece is about the other situation: you own fifty or five hundred repos, you cannot personally read every one, and you need to know what the rollout costs you if you sequence it wrong.
The failure that makes this expensive
Every other breaking change in recent memory announced itself. A flag was removed, the install died, someone fixed it before lunch. This one was designed not to do that. An unapproved install script is skipped with a warning and the install succeeds, which is the softer of the two options npm considered.
So the pipeline that would normally protect you reports success. npm ci exits 0. Tests that do not touch the native path pass. The image builds. The failure waits for the first request that reaches bcrypt, sharp, or whatever else needed a compiled binary, and then prints Cannot find module build/Release from inside a running container.
That is the whole problem in one sentence: the cost of this migration is not the work, it is the detection lag. Every repo you migrate without a gate is a repo where you have moved a build-time failure into runtime and not told anyone.
The five ways it actually breaks
Across the estate, the reports fall into five shapes, and they need different people to fix them:
- The silent skip. Install succeeds, a native addon never builds, the service dies on first use. Costs the most because it is found last.
EALLOWSCRIPTS. Either someone passed--allow-scriptsinside a project, which is rejected by design, or the repo pulls a git dependency and hits npm/cli issue #9783, where a user.npmrcvalue is forwarded into an inner project-scoped install and refused. The flag walkthrough separates the two, and approve-scripts vs allow-scripts covers which flag belongs where.EALLOWREMOTE. Usually a private registry, not a rogue dependency. TheRefusing to fetchline under the error carries the URL, and if it points at your own host you are looking at a path-prefix check, not a supply-chain problem. npm's config reference listsall,noneandrootas the accepted values, androotis the one most people actually want. Reaching for--allow-remote=allhere disables the control across every dependency you have, which is the wrong fix for a string comparison.- The arborist crash.
Cannot read properties of null (reading 'edgesOut')reads like a version conflict and is not. Onenpm install --package-lock-onlyon a clean checkout tells the three causes apart. - Broken audits. The legacy audit endpoints now answer
410 Gone, and the replacement can return a body that some clients cannot parse. A CI gate that treats a failed audit as a pass is worse than no gate, because it reports safety it did not check.
Only the first of those five is really about npm 12 policy. The rest are the estate telling you which repos were already unusual.
The order that keeps the failures visible
Sequence matters more than speed here, because the wrong order hides its own mistakes.
First, turn the warning into an error, in one repo. Add strict-allow-scripts=true and run npm ci. That converts the skip into an ESTRICTALLOWSCRIPTS failure at install time. You want the loud version before you touch anything else, because it is the only configuration in which the rest of the rollout can be trusted to report the truth.
Second, write the allowlist, do not hand-write it. npm approve-scripts --allow-scripts-pending lists what is pending, and approving writes the allowScripts block into package.json for you. It belongs in package.json and not .npmrc, which holds only the toggles around it. The schema reference has the pinned and name-only key forms.
Third, pin the CLI in the image before the fleet upgrades for you. The dangerous state is a mixed fleet: some runners on 11.x warning, some on 12 enforcing, the same commit behaving differently depending on which one picked it up. Decide when you cross that line rather than letting a base image decide.
Fourth, sort the exceptions by who can fix them. Private registry problems go to whoever owns the registry. Git dependencies go to whoever chose them. Native addons go to the service team, because only they know whether that binary is on a request path. Triaging these centrally is where rollouts stall.
Last, gate it. npm ci --strict-allow-scripts in CI, on every repo, permanently. Without it, the next unreviewed install script lands with a warning nobody reads.
What the control is worth once it is on
The point of the allowlist is not tidiness. An install script is the one place a dependency executes code on your build machine before anyone reviews a line of it, which is the window that fast-publish supply-chain attacks are built around, and the same window dependency cooldowns close from the release-timing side. Approving scripts by name turns that from a default into a decision.
Finished properly, you end up with three things: an allowlist per repo that a human signed off, a CI gate that fails on anything new, and a list of the repos that could not follow the standard pattern. The third one is the useful artifact. Those repos were carrying risk before npm changed anything.
Where this stops being a rollout
There is a point where this stops being a migration task and becomes a question about the build itself: which repos can execute unreviewed code, what a compromised dependency would reach from there, and whether anything downstream would notice. The npm 12 lockdown answers the first part for one package manager. It says nothing about CI permissions, what your workflows can read, or whether the image you ship can be verified by the thing that runs it.
That is the ground a supply-chain and CI review covers: the install-script surface and lockfile policy, GitHub Actions permissions and secret exposure, signing and verification, and a written finding list ranked by what an attacker reaches first. If you are mid-rollout and the exception list is growing faster than the migrated list, that is usually the signal that the problem was never really npm.
Comments
Be the first to comment.