npm 12 blocks install scripts by default — and Git dependencies, and remote-URL dependencies. The release landed on July 8, 2026, and it is the sharpest change npm has ever made to what happens when you type npm install: dependencies no longer get to run preinstall, install, or postinstall scripts unless your project explicitly allows them.
The stakes are not theoretical. The postinstall hook was the delivery mechanism for the most damaging npm supply-chain attacks of the past year — from the September 2025 wave of maintainer-account compromises and worm activity to the March 2026 Axios incident and the June 2026 Miasma worm that poisoned packages under Red Hat's official namespace. GitHub's own changelog described the old default as "the single largest code-execution surface" in the npm ecosystem.
This guide covers exactly what changed, which attack techniques the new defaults actually close (and which they don't), the silent-failure trap that can turn a green CI run into a production incident, and a step-by-step migration playbook built around the new npm approve-scripts and npm deny-scripts commands — including the monorepo and Docker gotchas most coverage skips.
- 01npm 12 flips three defaults at once.As of v12.0.0 (July 8, 2026), npm install no longer executes dependency lifecycle scripts, no longer resolves Git dependencies, and no longer resolves remote-URL dependencies unless each is explicitly allowed.
- 02npm was the last major package manager to move.pnpm blocked lifecycle scripts by default in v10.0.0 — January 2025, roughly 18 months earlier. Yarn Berry and Bun were already blocking. npm 12 closes the gap, it doesn't lead it.
- 03Blocked native builds can fail silently.Per TechTimes' analysis of the mechanism, an install whose native-module build was blocked still completes with exit code 0 — the missing binary only surfaces later as a runtime 'cannot find module' error. Smoke-test native modules explicitly.
- 04approve-scripts and deny-scripts are the migration tools.npm approve-scripts writes version-pinned allowlist entries to package.json (re-approval needed after updates); npm deny-scripts always writes name-only denials by design. Both require a project package.json.
- 05v12 narrows one vector — it does not end supply-chain risk.Install-time execution, the Git .npmrc bypass, and Phantom Gyp builds are closed. Compromised-maintainer runtime payloads and typosquatting remain open, and every source is explicit about that boundary.
01 — What ChangedThree defaults, flipped in one release.
The GitHub Changelog pre-announced the breaking changes on June 9, 2026, and v12.0.0 shipped them on July 8. The changelog's own wording is precise: "npm install will no longer execute preinstall, install, or postinstall scripts from dependencies unless they are explicitly allowed," Git dependencies "direct or transitive" will no longer resolve unless allowed via --allow-git, and remote-URL dependencies such as HTTPS tarballs will no longer resolve unless allowed via --allow-remote.
The scale of the trade is what makes the change defensible. Reported estimates circulating with the changelog coverage put the share of npm packages that genuinely need install scripts at roughly 2% — an estimate rather than a registry-wide audit — while under the old model, 100% of packages carried the implicit right to execute code on your machine at install time. Flipping the default converts a universal privilege into a reviewed exception.
allowScripts defaults off
Dependency lifecycle scripts no longer run unless the package is listed in your project's allowScripts field. This was the delivery mechanism for the Axios postinstall trojan and much of the 2025 wave of worm attacks.
--allow-git: none
Git dependencies no longer resolve by default. This closes a specific bypass: a malicious Git dependency's own .npmrc could override the system git executable path — achieving code execution even when --ignore-scripts was set.
--allow-remote: none
Dependencies fetched from remote URLs — HTTPS tarballs, direct or transitive — no longer resolve unless explicitly allowed. Registry-published versions with lockfile integrity hashes become the only default path.
binding.gyp file previously triggered an automatic, unconditional node-gyp rebuild at install time — with no lifecycle script entry and no --ignore-scripts protection. That implicit build now requires an allowScripts allowlist entry too. The June 2026 Miasma worm exploited exactly this gap.02 — Why NowTwelve months that forced the change.
The escalation started in September 2025, when a cluster of npm compromises hit in quick succession: a maintainer-account phishing hijack of widely used packages including debug and chalk — packages with combined weekly downloads exceeding 2.6 billion — followed within days by the self-replicating Shai-Hulud worm, which used stolen npm and GitHub tokens to propagate across hundreds of packages. CISA's advisory that week called it a "widespread supply chain compromise impacting npm."
March 31, 2026 brought the Axios incident: malicious versions 1.14.1 and 0.30.4 of a package pulling on the order of 70 to 100 million weekly downloads (the figure varies by source) were live on the registry for roughly a three-hour window. Per Microsoft's incident analysis, the attacker used a compromised maintainer account to publish a malicious dependency, plain-crypto-js, whose postinstall hook silently dropped a cross-platform remote-access trojan. Microsoft tracks the operation as Sapphire Sleet; Google Threat Intelligence attributes the same campaign to UNC1069 — both point to a North Korea state nexus. It followed the same playbook as the LiteLLM supply-chain attack on AI infrastructure earlier in the year: trusted package, install-time execution.
Then came June 2026 and the Miasma worm. A first wave compromised 32 packages under Red Hat's official @redhat-cloud-services namespace; a second wave two days later — documented by Wiz Research on June 4 — hit 57 more packages via the Phantom Gyp binding.gyp technique, all within roughly a two-hour window. The largest single victim was Vapi.ai's official voice-AI server SDK at 408,000+ monthly downloads. The payload harvested credentials across npm, GitHub, AWS, GCP, Azure, HashiCorp Vault, and Kubernetes — and used stolen GitHub Actions OIDC tokens to forge valid provenance attestations, meaning infected versions passed signature checks. Five days after Wiz's writeup, GitHub published the npm 12 breaking-changes announcement.
Malicious packages in 2025
Attackers published nearly 455,000 malicious open-source packages in 2025 alone, per Sonatype's 2026 State of the Software Supply Chain report — pushing the cumulative blocked total past 1.233 million, a 75% year-over-year jump.
Malicious versions live
Malicious axios 1.14.1 and 0.30.4 were active for roughly three hours (per Huntress, about 00:21 to 03:15 UTC) before takedown — on a package with tens of millions of weekly installs. The payload rode a postinstall hook.
Packages in wave two
Wiz Research documented 57 packages compromised via the Phantom Gyp binding.gyp technique in roughly a two-hour window on June 3-4. GitHub published the npm 12 breaking-changes announcement five days later.
03 — Closure MatrixWhat v12 actually closes — and what it doesn't.
Most coverage treats npm 12 as a single monolithic fix. It isn't — it is three specific defaults plus one implicit-build closure, and the boundary matters. The matrix below cross-references each attack technique from the 2025-26 wave against the legacy --ignore-scripts escape hatch, the new v12 defaults, the real-world incident that used it, and the migration action it demands. Two vectors stay open, and pretending otherwise is how teams get complacent.
| Attack vector | Legacy --ignore-scripts | npm 12 default | Real-world use | Migration action |
|---|---|---|---|---|
| Closed by npm 12's defaults | ||||
| Automatic preinstall / install / postinstall execution | Only if --ignore-scripts was set — opt-in, rarely enforced | Blocked — allowScripts defaults off | Axios, March 2026 — a malicious dependency's postinstall hook dropped a remote-access trojan | Approve genuinely needed packages via npm approve-scripts |
| Git dependency overriding the git binary via its own .npmrc | Not blocked — worked even with --ignore-scripts set | Blocked — --allow-git defaults to none | Documented bypass cited in coverage of the v12 changes | Re-add trusted Git dependencies explicitly with --allow-git |
| Phantom Gyp — implicit node-gyp rebuild triggered by a binding.gyp file | Not blocked — no lifecycle entry existed to ignore | Blocked — the trigger now requires an allowScripts entry | Miasma worm, June 2026 — 57 packages compromised in wave two | Approve native-addon packages that legitimately compile |
| Remote / HTTPS-tarball dependencies (direct or transitive) | Not blocked | Blocked — --allow-remote defaults to none | Closed pre-emptively — no single headline incident in our sources | Replace with registry versions, or allow explicitly |
| Still open after npm 12 | ||||
| Compromised maintainer account shipping malicious runtime code | Not blocked | Not blocked — the allowlist has no mechanism to catch it | September 2025 maintainer-account hijack of debug, chalk, and other widely used packages | Account security, provenance review, and dependency-review gates |
| Typosquatting and dependency confusion | Not blocked | Not blocked | Ongoing mass-poisoning campaigns tracked by Unit 42 through 2026 | Lockfiles, scoped registries, and name review at PR time |
The "still open" rows are not a footnote. The September 2025 chalk/debug hijack shipped malicious runtime code, not an install script — v12's allowlist would not have stopped it. Security-industry reaction to the release names this gap explicitly.
"Account takeover remains an open wound. Once an actor controls a legitimate maintainer's credentials, no amount of install-script blocking helps, because the malicious code ships as a trusted, signed release."— Moshe Siman Tov Bustan, OX Security, via Cybernews
04 — The TrapThe silent-failure trap: green builds, missing binaries.
Here is the part that will bite teams who treat the v12 upgrade as routine. Per TechTimes' analysis of the blocking mechanism, when v12 blocks a native module's build script, npm install still completes with exit code 0 and no error. Your CI pipeline goes green. The missing compiled binary only surfaces later — as a runtime "cannot find module" error, in staging if you're lucky, in production if you're not.
The commonly affected packages named in coverage are the ones most production JavaScript stacks quietly depend on: sharp, bcrypt, canvas, sqlite3, fsevents, bufferutil, esbuild, and @tailwindcss/oxide. Browser-automation tools are a second cluster: Puppeteer, Playwright, and Cypress all use postinstall hooks to download full browser binaries. The recommended fix is to call npx playwright install or npx cypress install as an explicit pipeline step rather than relying on the hook.
require()s (or imports) every native module your app depends on, and run it in CI immediately after install — before the build step. It's a five-minute script that converts a silent production incident into a loud, cheap pipeline failure.There's a preview lane, too: all three v12 behaviors are available as advisory, non-blocking warnings starting in npm 11.16.0. Installs still succeed and scripts still run, but the CLI flags every package that v12 would block. Running one 11.16+ install and reading the warnings is the cheapest possible dry run — do it before the major-version bump, not after.
05 — MigrationThe migration playbook: approve, deny, verify.
npm ships two new commands for the transition, both managing the allowScripts field of package.json. The sequence that works:
- Audit first. On npm 11.16+, run a normal install and collect the advisory warnings. Then run
npm approve-scripts --allow-scripts-pending— a read-only listing of every package whose scripts aren't yet covered byallowScripts, without writing anything. - Approve deliberately.
npm approve-scripts <pkg>writes a version-pinned entry by default (pkg@1.2.3). Pass--no-allow-scripts-pinfor a name-only, any-version approval — a wider trust grant you should reserve for packages you'd trust at any version. - Deny the rest.
npm deny-scriptsalways writes a name-onlyfalseentry, never version-pinned. That's a deliberate design choice — as npm's documentation puts it: "Pinning a deny to a specific version would silently re-allow scripts for any other version of the same package, which defeats the purpose; the command picks the safer default for you." - Verify. Run the native-module smoke test from Section 04, then run your full test suite. A green install proves nothing on its own.
Two scope limits to know: both commands require a project-level package.json and cannot be used with -g global installs or one-off npx runs.
Version-pinned approvals
npm approve-scripts <pkg> pins the approval to the exact version. Updating the package does not carry the approval forward — it must be re-approved after npm update. More friction, but every new version gets a conscious look.
approve-scripts --all
Approves every package with a currently-unreviewed install script in one pass. Fast for a first migration, but it reproduces the old implicit-trust model at a single point in time. Review the pending list before you reach for it.
deny-scripts (name-only)
Writes a permanent name-only false entry for packages whose scripts you never want to run. Useful for dependencies whose install scripts are telemetry, banners, or optional acceleration you don't need.
allow-scripts=all in .npmrc
Restores the pre-v12 behavior wholesale. Coverage is blunt about this one: it trades away the entire security benefit rather than migrating. Acceptable as a hours-long stopgap during an incident, not as a destination.
06 — CI & MonoreposMonorepos, Docker, and the gotchas coverage skips.
The per-workspace scoping rule is the trap most likely to hit teams running Turborepo or npm workspaces: allowScripts is scoped per workspace, and an approval at the repo root does not propagate to nested packages. Every workspace with native dependencies needs its own npm approve-scripts pass. Combine that with the silent exit-0 failure mode and a Docker multi-stage build — where the install happens in a builder stage nobody watches — and you have a green pipeline shipping an image with missing binaries. Teams on Next.js 16.3 build pipelines should audit both the app workspace and any internal packages that compile native addons.
For CI hardening beyond the upgrade itself, the direction the analyst community is converging on: treat unreviewed install scripts as a build failure, and verify native modules load rather than trusting install exit codes. Wire the approval check and the smoke test into the same pipeline stage — our CI/CD pipeline design reference covers where gates like this belong in a trunk-based flow.
How much work is this in practice? The Node-RED project — a widely used open-source IoT platform — publicly audited its own ecosystem and found 72 packages using install scripts, and was still deciding its migration approach as of mid-June coverage. The breakdown is instructive: the overwhelming majority are postinstall.
Where install scripts live · one real project's audit
Source: Node-RED ecosystem self-audit, reported June 13, 2026One more repeat-offender to schedule for: because approvals are version-pinned by design, every npm update that bumps an approved package invalidates its approval. Build the re-approval step into your dependency-update workflow — whether that's Renovate, Dependabot, or a monthly manual pass — or your first post-update install will silently skip the scripts you thought you had allowed.
07 — Ecosystem Contextnpm was the laggard, not the leader.
The underreported framing: npm 12 is not a bold first move — it is the last major JavaScript package manager to stop granting every dependency implicit install-time execution rights. pnpm shipped the equivalent policy in v10.0.0 on January 10, 2025, roughly 18 months earlier, as a direct response to a supply-chain attack on Rspack that distributed cryptomining malware via an automatic postinstall script, per pnpm maintainer Zoltan Kochan. Yarn Berry and Bun were already blocking by default.
| Package manager | Scripts blocked by default | Allowlist mechanism | Position vs npm 12 |
|---|---|---|---|
| pnpm | v10.0.0 — January 10, 2025 | onlyBuiltDependencies in package.json (pnpm 10 and earlier); unified allowBuilds map in pnpm-workspace.yaml managed via pnpm approve-builds (v11) | Roughly 18 months ahead of npm |
| Yarn Berry | Already blocking before npm 12, per July 2026 coverage | Yarn-specific configuration (not covered in our sources) | Ahead of npm |
| Bun | Already blocking before npm 12, per July 2026 coverage | Bun-specific allowlist (not covered in our sources) | Ahead of npm |
| npm | v12.0.0 — July 8, 2026 | allowScripts field in package.json, managed via npm approve-scripts and npm deny-scripts | Baseline — the last major package manager to flip the default |
For teams already on pnpm, the v11 mechanics are worth knowing because they mirror where npm just landed: pnpm approve-builds runs interactively without arguments or accepts package names directly, a ! prefix denies a package, and --all (pnpm 10.32.0 and later) auto-approves every pending build. pnpm 11 replaces the legacy onlyBuiltDependencies family with a unified allowBuilds map in pnpm-workspace.yaml.
Read as a trend rather than a release: the JavaScript ecosystem has now fully converged on deny-by-default install-time execution, about a decade after the same shift played out in browser permissions and mobile app sandboxing. What's notable is what forced each mover — pnpm reacted to one cryptominer incident; npm's steward waited until worms were forging provenance attestations under a Red Hat namespace. Defaults in infrastructure this entrenched move only when the cost of the status quo becomes undeniable, and the sequence here — Miasma wave two on June 4, breaking-changes announcement on June 9 — reads as exactly that tipping point.
08 — ImplicationsWhat this means for engineering teams and agencies.
The operational shift is bigger than one upgrade. Under the old model, dependency trust was a one-time decision made at npm install; under v12, it becomes a reviewed, version-pinned artifact committed to source control — and CI becomes the enforcement point. That's a standing process change, not a checkbox. GitHub, as npm's steward, is making the same bet here that it's making with its agent-native tooling push: defaults, not documentation, are what actually change developer behavior at ecosystem scale.
Looking forward, the projection we'd stake out: install-time attacks don't disappear, they migrate. With the postinstall door closing across all four major package managers, expect attacker effort to concentrate on the two vectors v12 leaves open — maintainer-account takeover delivering malicious runtime code, and typosquatting — plus social-engineering pressure on the approval step itself (packages that "need" scripts for plausible-sounding reasons). The teams that win this next phase are the ones that treat the allowlist as a reviewed security artifact, not a friction to be bulk-approved away.
If your team ships client work on Node-based stacks, budget a real migration window: audit on npm 11.16+, approve per workspace, wire the native-module smoke test into CI, and add re-approval to the dependency-update routine. That's the same hardening-by-default posture we bring to web development engagements, and for organizations rolling this out across many repos and teams at once, our AI transformation practice builds exactly this kind of pipeline-governance automation.
09 — ConclusionA better default, not a finish line.
Install-time trust is now explicit. Verification is now your job.
npm 12 is the right change, made late. Blocking install scripts, Git dependencies, and remote-URL dependencies by default closes the exact mechanisms behind the Axios trojan, the Git .npmrc bypass, and the Phantom Gyp worm wave — the attack techniques that defined the past twelve months of npm security incidents. pnpm proved the model works eighteen months before npm adopted it.
The migration itself is one command per package. The engineering work is everything around it: per-workspace approvals in monorepos, re-approval after every update, explicit browser-binary installs, and — above all — never trusting a green install again. The exit-0 silent failure is the single most likely way this change hurts a team that did everything else right.
And keep the boundary honest: v12 narrows install-time execution, nothing more. Compromised maintainer accounts shipping malicious runtime code, and typosquatting, remain open problems the allowlist cannot see. Treat npm 12 as one strong layer in a defense-in-depth posture — lockfiles, provenance review, account security, dependency-review gates — and it earns its breaking changes many times over.