Skip to content

Back to the catalog

Socket: npm v12 Ships With Install Scripts Off by Default
socket.devarticle

Socket: npm v12 Ships With Install Scripts Off by Default

npm v12 is now generally available, disabling preinstall/install/postinstall lifecycle scripts, the implicit node-gyp rebuild triggered by binding.gyp, git dependency resolution, and remote URL dependency resolution by default, all of which required explicit opt-in via allowScripts, --allow-git, and --allow-remote. The release also begins phasing out granular access tokens configured to bypass 2FA, first restricting their account/org actions (~Aug 2026) and later removing their ability to publish directly (~Jan 2027), pushing teams toward trusted publishing (OIDC) or staged-publish-with-2FA-approval workflows instead. The changes respond to a year of install-time supply-chain worms (Shai-Hulud, Miasma/Phantom Gyp) that exploited default script execution and binding.gyp-based implicit builds to evade scanners.

TL;DR npm v12 turns off install scripts, node-gyp implicit builds, and git/remote dependency resolution by default, and starts sunsetting 2FA-bypass publishing tokens in favor of OIDC/staged publishing.
Document

What it is

Operational detail on npm v12's new install-time defaults — the behaviors, settings, and specific packages an engineer needs to know before upgrading. The summary establishes what changed; this covers how it fails and what breaks.

How it works

The new default is a soft skip, not a hard failure: an unapproved preinstall/install/postinstall script is skipped, npm prints a warning, and the install still succeeds. Native modules are the trap — a skipped node-gyp build does not fail the install; it fails later at runtime when your code tries to load a module that was never compiled.

The --allow-git change also closes a specific hole: a git dependency's .npmrc could override the git executable and run code even under --ignore-scripts.

Preconditions / Affected

Packages that need explicit approval to function after upgrade:

  • Native modules: sharp, better-sqlite3, bcrypt, node-sass
  • Binary downloaders: Cypress, Playwright, Puppeteer, Electron

Anyone carrying legacy or tool-specific keys in .npmrc should note the walked-back breaking change (below).

flowchart TD
    A[npm install, v12 default] --> B{Script approved?}
    B -->|Yes| C[Runs normally]
    B -->|No, default| D[Skip + warn, install succeeds]
    D --> E{strict-allow-scripts set?}
    E -->|Yes, CI| F[Hard error]
    E -->|No| G[Native module: fails later at runtime]

What to do

  • For CI, opt into hard failure so skipped scripts don't pass silently: set strict-allow-scripts.
  • To review what needs approval, use npm approve-scripts --allow-scripts-pending, which lists packages with scripts and the command each runs. Note the gap: it does not show what the scripts do (files pulled, network calls, env reads).
  • .npmrc breaking change was reverted: unknown config file keys now warn instead of erroring (EUNKNOWNCONFIG). Opt into the strict check with strict-npmrc. Unknown CLI flags still error.
  • For native module authors: vlt's Darcy Clarke recommends shipping platform-specific binaries via optional dependencies (the esbuild model) instead of install scripts.

Caveats

  • The v12 backport to Node 24 and Node 26 is only hoped for — the decision belongs to the Node release team, not npm. Without it, the new defaults wait for Node 27.
  • The approve-scripts tooling shows commands but not behavior; deeper review (network/env/filesystem signals, new-vs-changed detection) exists only as a proposed RFC, not a shipped feature.

Threat actors named here