npm 12 Disables Install Scripts by Default, and Your CI Pipeline Is About to Tell You
npm 12 ships with lifecycle scripts, git dependencies, and remote URL deps all blocked by default. Here's what breaks, why it happened, and how to migrate without turning all the protections back off.

npm 12 shipped on July 8th, and three install-time behaviors that the ecosystem has taken for granted since npm 2 are now off by default. Lifecycle scripts don't run. Git dependencies don't resolve. Remote URL tarballs don't fetch. If your CI pipeline compiles native packages like sharp, canvas, or better-sqlite3, you're about to find out.
Here's what changed, what's going to break in your specific setup, and how to migrate without just disabling all the new protections.
The Three New Defaults
npm 12 flips three install-time behaviors to the safe side simultaneously:
**allowScripts defaults to off.** preinstall, install, and postinstall lifecycle scripts no longer run automatically. A package must be explicitly listed in your package.json's allowScripts config for its scripts to execute. This includes implicit node-gyp rebuilds — a native package with binding.gyp gets blocked even if it has no explicit install script, because npm was previously auto-running node-gyp rebuild for it.
**--allow-git defaults to none.** Git dependencies ("pkg": "github:org/repo") no longer resolve unless explicitly allowed. This closes an attack path where a git dependency's local .npmrc could override the git executable path.
**--allow-remote defaults to none.** Direct tarball URL dependencies ("pkg": "https://...") are blocked by default.
All three were flagged in security guidance for years without becoming defaults. The Miasma supply chain attack in June 2026 — which compromised build environments at multiple companies through a malicious postinstall in a transitive dependency — pushed npm to stop making these opt-in.
What Actually Breaks
The most common breakage is native addons. Packages like sharp, canvas, better-sqlite3, fsevents, and bcrypt need install scripts to compile from C++. In npm 12 they're skipped silently — you get a warning, not a hard error — which means your app installs successfully but then crashes at runtime when it tries to load the missing native binary.
Git dependencies are the second category. If your monorepo links internal packages via git URLs rather than a private registry, those are a hard failure — not a warning. Fix: publish to a registry. Even a local Verdaccio instance is fine; the point is to get off git-at-install-time.
Remote tarballs are niche but real in enterprise settings where teams host private builds on S3 or an internal CDN. Each allowed URL needs an explicit --allow-remote flag.
How to Migrate
npm 11.16+ shipped migration tooling specifically for this transition. The workflow:
# Audit what's blocked without changing anything
npm approve-scripts --allow-scripts-pending
# Interactive approval — shows source of each script
npm approve-scripts
# Approve everything currently in your tree, tighten later
npm approve-scripts --allWhat you approve gets written to package.json. Crucially, approvals are **version-pinned by default**:
{
"allowScripts": {
"[email protected]": true,
"[email protected]": true,
"@img/[email protected]": true,
"[email protected]": true
}
}The version pinning is intentional. If sharp releases 0.33.6 with a compromised install script, your existing approval doesn't cover it. You'll see a new warning, review the updated script source, and approve again deliberately. Pass --no-allow-scripts-pin if you want name-only approvals that carry across versions — but you're giving up the per-version audit trail.
You can also use npm deny-scripts <pkg> to explicitly block a package. Denied entries survive --all and future review prompts — useful for packages you've audited and decided should never need install scripts.
CI/CD: What to Change Now
If you pin npm in CI via npm install -g npm@12, add allowScripts to package.json at the same time. Installing npm 12 without the allowlist means your native packages silently install without compiling their binaries. You won't know until a test fails or your container crashes under real load.
Don't use this as a permanent fix in your pipeline config:
# Do not commit this to your CI
npm install --allow-scripts-matching "*"It disables the protection entirely, doesn't survive PR review, and gives you no audit trail. The allowlist in package.json is the right place — it's version-controlled, shows up in diffs, and is reviewable by your team.
For Docker builds: if your Dockerfile runs npm ci against a pinned node image, check the npm version first. Official node:22 images ship npm 10 as of this writing. You're safe until you explicitly upgrade the npm version in your image.
Why Now
The ecosystem has had years of warnings. event-stream, colors, node-ipc, XZ Utils — install scripts have been the enabling layer in dozens of supply chain compromises. npm's previous position was that disabling them by default would break too many packages. That position held until it didn't.
The Miasma attack in June 2026 changed the calculus. Miasma's malicious payload landed in a transitive dependency — not anything in anyone's direct dependencies list — and executed on install across every affected project. That's exactly the scenario explicit allowlists prevent: you can't approve something you don't know is in your tree.
Deno and Bun never auto-ran install scripts. It was a deliberate design choice and one of the reasons both runtimes got traction on the security-conscious side of the ecosystem. npm is catching up.
The Actual Take
The list of packages that legitimately need to run code on your machine during install is short. For most projects it's five to ten packages, all native addons you've been using for years. Writing that list into package.json costs you fifteen minutes once. In exchange you get a reviewable trust boundary, protection against transitive dependency compromise, and a file that answers "which packages are we trusting with code execution?" with an auditable answer instead of "all of them."
If you maintain a package that uses install scripts for non-native work — generating types, downloading schemas, running codegen — this is the moment to fix that. Move it into a prepare script that runs at publish time, or make it an explicit postinstall in your own app's package.json so the consumer controls it. Don't ask your users to approve your convenience script. The install scripts era had a good run. Be explicit.