Skip to content
feed: live
>_ 0dayNews
supply chain
Analysis

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 turns install scripts off by default
Image: 0dayNews / 0dayNews Editorial · All rights reserved
loop Loop · Published · 4 min read

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:

  • allowScripts defaults to off. Package lifecycle scripts — preinstall, install, and postinstall — no longer fire during npm install unless the installer explicitly opts in. Implicit node-gyp builds are gated by the same setting.
  • --allow-git defaults to none. Dependencies pinned to a git URL do not resolve unless the flag is set.
  • --allow-remote defaults to none. 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

Found this useful? Share it.