DEV Community

Etairos.ai
Etairos.ai

Posted on • Originally published at threat-intelligence.redeyesecurity.com

npm 12 Kills Install Scripts by Default, Ending the Postinstall Attack Era

TL;DR

  • what: GitHub released npm 12 with preinstall/install/postinstall scripts, git dependencies, and remote-URL dependencies disabled by default, and announced a two-phase deprecation of 2FA-bypass granular access tokens.
  • impact: The default code-execution primitive behind nearly every major npm supply chain worm and credential stealer no longer fires automatically, but builds relying on native modules or lifecycle scripts will break until scripts are explicitly allowlisted.
  • fix: Upgrade to npm 12, run npm approve-scripts --allow-scripts-pending to build a committed allowlist in package.json, and move automated publishing to trusted publishing (OIDC) or staged publish before the January 2027 token cutoff.
  • who: Every JavaScript development team, CI/CD pipeline owner, and package maintainer using npm tokens for automated publishing.

GitHub has shipped npm 12, and with it the most consequential default change in the package manager's history: install scripts no longer run. The preinstall, install, and postinstall lifecycle hooks that have powered nearly every major npm supply chain attack, from event-stream in 2018 through the Shai-Hulud worm and the chalk/debug compromises of 2025, are now opt-in. Arbitrary code execution at install time, the feature attackers relied on to steal CI credentials and self-propagate across the registry, is off unless you explicitly turn it on per package.

Three behaviors flipped from automatic to opt-in

npm 12 changes three defaults at once, and each closes a distinct attack path. First, allowScripts defaults to off: dependency lifecycle scripts and implicit node-gyp builds no longer execute during install. Second, --allow-git defaults to none, so git dependencies, whether declared directly or pulled in transitively, are not resolved. Third, --allow-remote defaults to none, blocking dependencies fetched from arbitrary remote URLs such as https tarballs. The git and remote-URL changes matter more than they look: both were quiet ways for a compromised transitive dependency to pull code from infrastructure the registry never sees, bypassing registry-side scanning entirely.

To restore scripts for packages that genuinely need them, developers run npm approve-scripts --allow-scripts-pending, review what npm surfaces, and commit the resulting allowlist to package.json. That commit step is the point. The allowlist lives in version control, goes through code review, and produces a diff when someone adds a new script-running package. A postinstall payload added to a compromised dependency now needs a human to approve it in a reviewable change rather than executing silently on the next npm install.

⚠️ Expect breakage, budget for it now — Packages that compile native code (node-gyp builds), download platform binaries, or run setup scripts will silently stop working until allowlisted. GitHub previewed these warnings in npm 11.16.0. If you have not already upgraded to 11.16.0 or newer and reviewed the install warnings, do that before rolling npm 12 into CI, or your first pipeline run will tell you the hard way.

2FA-bypass tokens are on a two-phase death march

The second half of the announcement targets the credential side of the supply chain problem. Granular access tokens (GATs) configured to bypass 2FA, the long-lived secrets sitting in CI systems that attackers exfiltrated at scale during the 2025 token-theft campaigns, are losing their power in two stages.

  • Early August 2026: 2FA-bypass GATs can no longer perform sensitive account, package, or organization actions, including creating or deleting tokens, generating recovery codes, changing passwords, email, or 2FA settings, modifying package access, maintainers, or trusted publishing configuration, and managing org and team membership.
  • January 2027: GATs lose direct publish entirely; their publishing surface shrinks to reading private packages and staging a publish that only goes live after a human approves it with 2FA.

Read the second change carefully because it ends a whole class of incident. A stolen publish token today means an attacker can push malicious versions of every package the token touches, immediately. After January 2027, that same stolen token gets the attacker a staged publish waiting for a 2FA approval that never comes. GitHub's stated path forward is trusted publishing via OIDC, where CI proves its identity per-run and no long-lived secret exists to steal, or staged publishing with a human approval gate.

Why the ecosystem is converging on this

npm is not moving alone. pnpm 11.10 introduced a structured, URL-keyed _auth setting that binds a registry credential to the host it belongs to, and reads it only from the environment or global config, never from files inside a project. As Socket noted, that closes a common foothold: a tampered .npmrc or pnpm-workspace.yaml in a cloned repository can no longer redirect a valid token to an attacker-controlled host. Between disabled install scripts, dying publish tokens, and host-bound credentials, the JavaScript ecosystem is systematically removing the three primitives that made 2024 and 2025 the worst years on record for registry attacks: code execution on install, long-lived publish secrets, and credential redirection.

The security math changed — Attackers compromised maintainer accounts because a single postinstall script in a popular package meant code execution on hundreds of thousands of machines within hours. With scripts off by default, that same compromise yields inert code sitting in node_modules until someone deliberately allowlists it. The payoff for account takeover just dropped by an order of magnitude.

What your team should do this month

  • Upgrade developer machines and CI images to npm 12; if you need a transition period, move to 11.16.0+ first and triage the install warnings it emits.
  • Run npm approve-scripts --allow-scripts-pending in each repository, review every package requesting script execution, and commit the allowlist; treat additions to it as security-relevant changes in code review.
  • Inventory every npm GAT configured to bypass 2FA, stop using them for account, package, and org management before August 2026, and perform those operations interactively with 2FA.
  • Migrate automated publishing to trusted publishing (OIDC) now rather than waiting for the January 2027 deadline; if OIDC does not fit your pipeline, design the staged-publish human approval step into your release process.
  • Audit for git and remote-URL dependencies in your lockfiles; anything that breaks under --allow-git=none or --allow-remote=none deserves scrutiny about why it was fetching code outside the registry.

Secure-by-default packaging has been the ask from the security community for the better part of a decade. npm 12 delivers it, and the cost is a one-time allowlisting exercise per repository. Teams that do the migration deliberately this quarter get the protection without the outage. Teams that ignore it get both the breakage and, until they upgrade, continued exposure to the install-script attacks everyone else just became immune to.


Originally published on RedEye Threat Intelligence.

Top comments (1)

Collapse
 
circuit profile image
Rahul S

Genuinely good change, but worth being clear about what it doesn't cover: install scripts were the loudest primitive, not the only one. A package that does its work in top-level module code still runs the moment something imports it — your test runner, a bundler walking the module graph, whatever — and no lifecycle hook was ever involved, so approve-scripts doesn't touch that path at all. The other thing I'd watch is the allowlist becoming muscle memory: the first time a legit native dep like sharp or better-sqlite3 breaks without its build step, people are going to bulk-approve, and you're right back to the prompt-fatigue that made postinstall attacks land in the first place.