On 28 October, Node 26 becomes Active LTS. Most teams will upgrade to it over the following weeks and reasonably assume they are on current defaults. They will not be: the installer still bundles npm 11, and npm 12 — the security release that stops dependency install scripts from running by default — will not arrive with any Node upgrade before Node 27 in 2027.
This article shows what npm 12 actually blocks, why the bundled npm will not bring it to you, and how to install it yourself and migrate to its allowlist without breaking your native modules or your CI.
What npm 12 stops running
Until npm 12, npm install ran the preinstall, install and postinstall scripts of every package in your tree, transitive dependencies included. Install meant execute: one compromised package anywhere in the tree ran code on every developer machine and CI runner that installed it.
npm's own announcement calls install-time lifecycle scripts the single largest code-execution surface in the ecosystem, and npm 12 closes it. Three npm install defaults change:
- Dependency install scripts are off.
preinstall,installandpostinstallfrom dependencies are skipped unless yourpackage.jsonapproves them. - Git dependencies stop resolving.
--allow-gitdefaults tonone. - Remote-URL dependencies stop resolving.
--allow-remotedefaults tonone;--allow-fileand--allow-directorykeep their old defaults.
The block also covers scripts nobody declared. Any package with a binding.gyp triggers an implicit node-gyp rebuild, and npm 12 blocks that too, along with prepare scripts from git, file and link dependencies.
The attacks these defaults answer
This is not a theoretical hardening. Every major npm supply-chain campaign of the past year executed through install-time scripts, before a single line of the package was ever imported.
Source: Group-IB, Microsoft Threat Intelligence, StepSecurity, Red Hat, 2025–2026
| Packages | |
|---|---|
| Shai-Hulud · Sep 2025 | 800 |
| ChainDrop · Aug 2026 | 400 |
| Mastra · Jun 2026 | 140 |
| @redhat-cloud-services · May 2026 | 32 |
The ChainDrop worm ran from an npm preinstall hook before installation even completed, harvesting npm, GitHub, AWS and Vault credentials and republishing itself with the stolen tokens. Elastic's first remediation line for it was blunt: upgrade to npm 12, which blocks preinstall hooks by default.
The pattern has not slowed since. September's axios compromise smuggled a remote-access trojan in through a dependency's postinstall, and on 5 October StepSecurity found a credential-stealing payload in @subql/common.
Why your Node 26 upgrade doesn't bring it
npm majors normally reach you bundled with a Node release. Not this time: the Node.js Release Working Group decided not to land npm 12 on Node 26, 24 or 22 — it shipped too late for Node 26 and carries too many breaking changes for an LTS line.
So the dates fall badly:
| Date | What happens | Bundled npm |
|---|---|---|
| 8 Jul 2026 | npm 12.0.0 ships as a security release | — |
| 20 Oct 2026 | Node 24 (Krypton) moves to Maintenance LTS | 11.x |
| 28 Oct 2026 | Node 26 becomes Active LTS | 11.x |
| Apr 2027 | Node 27 is Current — the next chance for a bundled npm 12 | TBD |
| Oct 2027 | Node 27 becomes LTS | TBD |
The LTS dates come from NodeSource's October schedule summary; Node 27's from the new annual release schedule. On the day npm 12 shipped, Node 26.5.0 shipped alongside it still bundling npm 11.17.0.
Waiting for the bundled npm keeps the protections off for another year, through the ecosystem's worst run of install-script attacks.
Install npm 12 yourself
The good news: you do not need Node 27. npm 12's own engines line is ^22.22.2 || ^24.15.0 || >=26.0.0 (npm 12 changelog), so every currently supported Node line can run it. This is exactly how large projects such as Gutenberg are adopting it: decoupled from Node, installed explicitly.
$ node -vv26.10.0$ npm -v11.17.0$ npm install -g npm@12$ npm -v12.0.2Build your allowlist
After upgrading, npm install warns about every dependency script it skipped. The migration npm recommends is to snapshot what you already run, then tighten:
$ npm installnpm warn install-scripts 3 packages had install scripts blocked$ npm approve-scripts --allow-scripts-pending$ npm approve-scripts --all$ git add package.json$ git commit -m "chore: snapshot install-script allowlist"--allow-scripts-pending is read-only — it lists what needs a decision without changing anything. approve-scripts writes the allowlist into package.json and pins each approval to the installed version by default, so a hijacked later release is not pre-approved. From that commit on, you are protected against any new or changed script entering your tree.
Adding a new package with a script becomes a three-step loop instead of one command:
$ npm install -D esbuildnpm warn install-scripts esbuild postinstall skipped$ npm approve-scripts esbuild$ npm rebuild esbuildFor global installs and npx, approve-scripts refuses to run — there is no project package.json to write to. Use the config instead: npm install -g --allow-scripts=canvas,sharp <pkg>, or persist it with npm config set allow-scripts=canvas,sharp --location=user (the announcement's migration recipes).
The traps
An old ignore-scripts=true silently wins
Many teams already carry ignore-scripts=true in an .npmrc from an earlier hardening pass. While it is set, it takes precedence and no scripts run — the allowlist does not override it, and the same goes for a CI-wide npm_config_ignore_scripts environment variable. Build the allowlist first, then remove the blanket setting:
- ignore-scripts=trueStrict mode and the package you haven't installed yet
strict-allow-scripts=true turns the skip into a hard error that fires before npm writes anything. A new package therefore never lands in node_modules, and approve-scripts then errors because there is nothing on disk to approve. The npm team's own guidance: keep strict mode in CI, not on dev machines — locally, the soft default plus approve-scripts plus rebuild is the workflow.
Native modules fail later, not at install
A blocked postinstall is not an install error. sharp, better-sqlite3, bcrypt and friends install fine with their build skipped, then fail at runtime when the compiled binding is missing — on npm 12 the failure moves from where you expect it to where you don't. Approve node-gyp and each native package explicitly, and grep the pending list for them first:
$ npm approve-scripts --allow-scripts-pending | grep -Ei "gyp|sharp|sqlite|bcrypt"allow-git is an enum, not a boolean
--allow-git and --allow-remote take none, root or all. A commenter in the migration thread reports that allow-git=true is treated as unset — a config that looks fixed and still fails. root only covers git dependencies declared in your root package.json, so a single transitive git dependency forces you to all; audit with a grep over your lockfile before npm 12 reaches CI.
Lock it in CI
The bundled npm on your CI image is npm 11, so the pipeline needs the same explicit install — and it is the right place for strict mode, because everything there is already approved and committed.
jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 26 - run: npm install -g npm@12 - run: npm config set strict-allow-scripts true --location=project - run: npm ci - run: npm testNow an unreviewed script fails the build loudly instead of being skipped quietly, and the only way to add one is a reviewed change to package.json. Treat that field accordingly: allowScripts is a code-execution permission list, so a pull request that expands it deserves the same scrutiny as a change to your deploy keys.
Before 28 October
- Run
npm -von dev machines and CI; anything starting with 11 has the protections off. - Install npm 12 explicitly on one project:
npm install -g npm@12. - Snapshot the allowlist: install, review
--allow-scripts-pending, approve, commit. - Remove any
ignore-scripts=truethat would silently override the allowlist. - Approve
node-gypand every native module, then run the app, not just the install. - Grep lockfiles for
git+and remote tarball URLs before--allow-git=nonehits CI. - Turn on
strict-allow-scriptsin CI only.
Upgrade to Node 26 by all means — it is the right move. Just carry npm 12 in with it, because this is the rare security default that will not come to you on its own.
Preparing for npm v12: install scripts and non-registry sources become opt-in · community · Discussion #198547Hi everyone — sharing this so maintainers, application developers, and CI operators have time to prepare for behavioral changes landing in npm v12 (estimated July 2026). Everything below is already...github.com