npm 12 turns install scripts off by default
npm 12 defaults allowScripts to off and deprecates 2FA-bypass tokens. Closes the install-hook branch; does not touch the maintainer-account one.
npm 12 shipped on July 8, and the changelog moves two defaults that have
been the subject of supply-chain post-mortems for the better part of a
decade. allowScripts now defaults to off. Granular access tokens
configured to bypass two-factor auth are on a phased kill schedule. The
Hacker News’ July 9 writeup
walks through both.
What changed at the client
Two client-side defaults flipped, and one publisher-side deprecation started running its clock.
On the install path:
allowScriptsdefaults to off. Package lifecycle scripts —preinstall,install, andpostinstall— no longer fire duringnpm installunless the installer explicitly opts in. Implicitnode-gypbuilds are gated by the same setting.--allow-gitdefaults tonone. Dependencies pinned to a git URL do not resolve unless the flag is set.--allow-remotedefaults tonone. Dependencies pinned to a remote tarball URL do not resolve unless the flag is set.
The opt-in mechanism is npm approve-scripts --allow-scripts-pending,
which walks the current dependency set, prompts for each script that
wants to run, and writes an allowlist into package.json. The allowlist
is meant to be committed and reviewed like any other dependency change.
On the publish path, granular access tokens (GATs) are being deprecated in two phases. In early August 2026, GATs configured to bypass 2FA lose the ability to perform sensitive account and package operations. In January 2027, GATs lose the ability to publish at all; publishing has to go through OIDC trusted publishing or a staged pipeline with a human-approved 2FA prompt at the top. GATs retain read access to private packages either way.
Why the defaults were the way they were
The install-script default was not on because npm thought it was a good
idea in 2026. It was on because turning it off used to break a
nontrivial slice of the registry. Postinstall hooks compiled native
modules through node-gyp, pulled platform-specific binaries, generated
TypeScript output, or ran configuration steps a maintainer did not want
the caller to have to remember. The number of packages that genuinely
need to run something at install time has fallen over the last several
years — prebuilt binaries via @napi-rs and prebuildify, Rust
distribution via napi, ESM defaults, and the general decline of
ad-hoc setup steps have all reduced the surface. The default flip is
what happens when the ecosystem cost of the change falls below the
ecosystem cost of the compromises.
The GAT bypass default has a similar shape. Long-lived tokens made CI/CD publishing easy and made the failure mode of a leaked token catastrophic. Both facts have been true for a long time. What changed is that OIDC-based trusted publishing exists and works for most CI providers, so “we can’t require 2FA on every publish because CI can’t do it” is no longer load-bearing.
What it does not cover
allowScripts: false addresses one specific delivery path — the
malicious payload that fires at npm install time, before any
downstream code has been called. That path has been the class most of
the noisy npm supply-chain compromises used, and turning it off by
default is a real reduction in the attack surface a stock npm install
presents.
It does not address the class of compromise where a legitimate
maintainer’s authenticated publish pipeline delivers malicious code
that only fires when downstream application code calls the library.
This week’s @injectivelabs/sdk-ts@1.20.21 compromise is the current
textbook example: a trackKeyDerivation() function inserted into the
SDK’s legitimate key-derivation flow, batched over two-second windows,
beaconed over HTTPS to a domain crafted to look like Injective’s own
testnet infrastructure. No install-time hook. No leaked token. The
OIDC trusted-publisher chain was doing its job — proving the release
came from the project’s authorized pipeline — while a compromised
maintainer account walked the payload through it. Details in our
coverage of the Injective
SDK compromise.
The same-shape failure has shown up before. The xz-utils compromise disclosed in March 2024 rode a legitimate contributor account that had spent nearly two years accumulating standing in the project before landing the backdoor. Registry-side defaults do not reach either of these classes; from the registry’s perspective, both attackers were the right person publishing the right package.
One thing to actually do
If you maintain packages and publish from CI with a GAT, move to OIDC trusted publishing before the August cutover, not on it. GitHub’s migration guidance — surfaced in The Hacker News’ writeup — points at the two supported paths: trusted publishing via OIDC, or staged publishing with a human approver. Either is safer than a rotated GAT. Long-lived tokens will keep working for read operations after January, but they will not publish, and the transition burns fewer hours if it is done ahead of the deadline than into it.
If you install packages, upgrading to npm 12 will trip the new
allowScripts gate on any dependency that runs a script at install
time. Run npm approve-scripts --allow-scripts-pending, read the
listed scripts, and only approve the ones a package actually needs to
run. The default-off matters exactly to the degree that the allowlist
stays audited. A blanket approve is the version of this change that
accomplishes nothing.
Sourcing
- The Hacker News: npm 12 Disables Install Scripts by Default to Reduce Supply Chain Risk — 2026-07-09
- Related: Injective SDK’s npm compromise, and the OIDC that let it — 2026-07-10
- Related: JFrog’s rollup-polyfill follow-up: six more DPRK-linked npm packages — 2026-07-05
- Related: PolinRider: 108 DPRK npm packages inside Contagious Interview — 2026-07-04
- Related: HalluSquatting weaponizes AI-hallucinated npm packages — 2026-07-08
Found this useful? Share it.


