npm 12: Install Scripts Are Opt-In Now

On July 8, 2026, npm 12 reached general availability with the biggest change to npm install behaviour in years: lifecycle scripts — preinstall, install, and postinstall — no longer run automatically. They need explicit approval, and that approval is written into package.json where it gets reviewed in a pull request like any other code.

The problem: npm install meant "run their code on your machine"

Since the registry's early days, any package could declare an install script that executes arbitrary commands with the privileges of whoever typed npm install. No import required, no runtime entry point — installation alone was enough. That is precisely the path exploited repeatedly through 2026 in a run of registry compromises, including the AsyncAPI generator package compromise that Microsoft documented in July 2026.

The surface is not limited to direct dependencies either. A tree of 1,200 packages is 1,200 execution opportunities, most of them unread and published by people no one on the team has heard of.

What actually changed

Three new defaults land in npm 12:

  • allowScripts now defaults to off. Lifecycle scripts and implicit node-gyp builds do not run unless allowed.
  • --allow-git defaults to none. Git dependencies — direct or transitive — are no longer resolved without explicit permission.
  • --allow-remote defaults to none. Dependencies from remote URLs, such as https tarballs, are blocked by default.

--allow-file and --allow-directory keep their previous defaults.

The practical migration path

The smart move is discovering the blast radius before CI turns red. Start on npm 11.16.0 or newer, which warns about uncovered scripts while still executing them:

bash
npm i -g npm@latest
npm approve-scripts --allow-scripts-pending   # lists pending packages, changes nothing

Then approve only what genuinely needs it, package by package:

bash
npm approve-scripts sharp esbuild

Approvals are pinned to the installed version (pkg@1.2.3) unless --no-allow-scripts-pin is passed. To deny something explicitly:

bash
npm deny-scripts some-package

And to enforce v12 behaviour early inside CI — failing the build instead of silently skipping scripts:

ini
# .npmrc
strict-allow-scripts=true

With that in place, npm ci becomes a real gate: any new dependency that tries to execute code at install time stops the pipeline and asks for a human review.

Real-world caveats

Binary packages feel it first. Anything that downloads a prebuilt executable or compiles a native addon depends on postinstall. Blocking it without approval produces a build that succeeds and then fails at runtime with a misleading error. Approve those deliberately, and record the reason in the PR description.

Version pinning creates noise. Every dependency bump invalidates the pinned approval, so automated PRs will carry extra review steps. That is not a defect — it is the point: a review every time the code that will execute changes.

Git dependencies break internal forks. Teams pointing at a patched fork over a Git URL now need --allow-git explicitly, or a move to a private registry.

There is a second date on the calendar. The hardening does not stop here. From early August 2026, granular access tokens configured to bypass 2FA lose the ability to perform sensitive operations, and from January 2027 they lose direct publishing entirely. The recommended replacements are trusted publishing via OIDC or staged publishing with a human 2FA approval.

Takeaway

npm 12 moves install-time code execution from "default and silent" to "explicit and reviewed". This is not cosmetic hardening — it closes the widest exploitation path in the JavaScript ecosystem and puts the decision where it belongs: in code review.

A team that goes through this transition today on npm 11.16 with strict-allow-scripts=true enters v12 with a clean, understood allowlist. A team that defers it will discover the list on the day the pipeline stops.

For more on riding the ecosystem's security-update wave, see the Next.js July 2026 security release.

Primary source: npm install-time security and GAT bypass2fa deprecation — GitHub Changelog