npm v12: Dependency Install Scripts No Longer Run Automatically
With npm v12 (July 2026), npm install stops auto-running dependency install scripts. We reproduce a postinstall running on its own, explain what changes, the benefits you never noticed, and how to prepare today.

Makoto Horikawa
Backend Engineer / AWS / Django
With npm v12 (July 2026), npm install stops auto-running dependency install scripts. We reproduce a postinstall running on its own, explain what changes, the benefits you never noticed, and how to prepare today.
The bottom line: npm v12 stops "npm install" from silently running scripts
In npm v12, slated for July 2026, the behavior of the command you type every day, npm install, changes. Until now, the "install-time scripts" shipped by the dependencies you install were run automatically, without your permission. From v12, that is blocked by default, and only the scripts you explicitly allow will run.
For anyone who writes JavaScript or TypeScript, npm install is probably the single most-typed command. When the default behavior of that command changes, the blast radius is wide. And the reason for the change is the supply-chain attacks that have plagued npm for years.
"A dependency's code runs with my privileges just because I installed it" can be hard to picture. So before anything else, I ran it on my own machine to see it happen. Look at the terminal output below, which stands in for a screenshot.
A single npm install let code planted in a dependency write a file into my home directory. The screen only said "added 1 package." By default there is not even a trace shown that anything ran — and that is the behavior v12 removes.
What was npm install actually doing all this time?
When you install a package with npm (Node.js's standard package manager), npm doesn't just download and unpack the dependency. If a package's package.json declares lifecycle scripts, npm runs them at set moments. The three most common are:
- •
preinstall: runs right before installation - •
install: runs during installation (used for building native modules, etc.) - •
postinstall: runs right after installation (the most commonly used one)
The problem is that these run automatically not only for your own project, but for your dependencies (and their dependencies, and so on). Code from a package buried deep in node_modules — one whose name you've never heard — runs with your user privileges the moment you type the command.
And it isn't only scripts that were resolved automatically before v11. Here is what npm officially lists as changing in v12.
| Done automatically before v11 | What it means |
|---|---|
| Install-time scripts | Auto-runs a dependency's preinstall / install / postinstall |
| Native builds | Auto-runs node-gyp rebuild for packages with a binding.gyp |
| prepare scripts | Auto-runs prepare for git / file / link dependencies |
| Git dependencies | Auto-resolves references to repositories such as GitHub |
| Remote-URL dependencies | Auto-resolves URL references such as HTTPS |
In v12, everything in this table becomes "won't run unless explicitly allowed."
I tried it: a dependency's postinstall runs on its own
Words alone don't land, so I reproduced it with the npm on my machine (npm 10.9.7 / Node.js 22 at the time of writing; the auto-run behavior is identical through v11). What I did is simple: I wrote a single malicious-dependency stand-in and added it to an ordinary project.
The dependency's package.json is just this. Its postinstall writes a file into the home directory (a real attack would steal credentials or exfiltrate data here).
{
"name": "evil-dep",
"version": "1.0.0",
"scripts": {
"preinstall": "echo 'dependency code is now running with my privileges'",
"postinstall": "node -e \"fs.writeFileSync(os.homedir()+'/.npm-demo-pwned', 'stolen')\""
}
}Now run npm install in a project that depends on it. What's scary is that by default, nothing is shown.
$ npm install
added 1 package in 300ms
$ ls -a ~ | grep npm-demo
.npm-demo-pwned # <- and anyway< file pre ran script the wrote>
It only said "added 1 package," yet .npm-demo-pwned now exists in the home directory. The script definitely ran, but that fact never appears on screen. Only with --foreground-scripts can you finally see what happened inside.
$ npm install --foreground-scripts
> evil-dep@1.0.0 preinstall
> echo 'dependency code is now running with my privileges'
dependency code is now running with my privileges
> evil-dep@1.0.0 postinstall
> node -e "...write a file into the home directory..."
wrote a file into the home directory (= arbitrary code execution succeeded)
added 1 package in 98ms
This is the pre-v12 default. The behavior closest to v12's new default can be felt on today's npm with --ignore-scripts (strictly speaking, --ignore-scripts also stops your own project's scripts, whereas v12 only stops dependencies' scripts — a more surgical change).
$ npm install --ignore-scripts
added 1 package in 87ms
$ ls -a ~ | grep npm-demo
# <- blocked< created. nothing pre script the was>
Just to be sure, I checked the default value. npm config get ignore-scripts returns false. In other words, "don't ignore scripts = run them automatically" is the current default. It helps to think of v12 as effectively flipping that default.
Why change it: this was the main route for supply-chain attacks
Why is npm going this far? Because the postinstall I reproduced above is the classic entry point for supply-chain attacks targeting npm. Attackers hijack a popular package, or publish a typosquatted fake, and plant malicious code in its postinstall. Then they just wait for someone to npm install. The victim does nothing, yet the code runs.
This isn't theoretical. We've covered real incidents on this blog many times.
-
•
The case where a RAT (remote-access trojan) was slipped into axios, a package with 100 million weekly downloads
-
•
The TanStack→Nx Console attack, where 84 poisoned npm versions cascaded into a VS Code extension
-
•
The Guardrails AI case, where a poisoned package was mixed into an AI library
"Then just always pass --ignore-scripts," you might think. npm acknowledges that, and the v12 RFC explains that a .npmrc bundled in a git dependency can override the path to the git executable, providing a route to code execution even when you use --ignore-scripts. In other words, the old self-defense left a hole. So the decision is to tilt the default itself to the safe side.
If you want to audit your dependencies for vulnerabilities yourself, see our OSS supply-chain scanner (paste your npm / Python dependencies for an instant check).
What exactly changes in npm v12
The heart of v12 is not a ban but an opt-in. Rather than making scripts unusable, it switches to a model where you name the ones you want to run and allow them. The allowlist is recorded in a new allowScripts field in package.json, and committing it to git to share across the team is recommended.
You manage this with two new commands added to npm.
What you want to do
Command / flag
List dependencies that would be blocked
npm approve-scripts --allow-scripts-pending
Allow a dependency you trust
npm approve-scripts
Explicitly deny a dependency
npm deny-scripts
Allow git dependencies
--allow-git
Allow remote-URL dependencies
--allow-remote
Another key point: approvals are pinned per version. For example, approving esbuild@0.21.5 does not silently extend to esbuild@0.22.0. Every time a version bumps, a human re-checks it. Hijacks tend to happen when "a package you trusted ships malicious code in some later update," so this version pinning makes sense.
The release is slated for July 2026. It won't arrive out of nowhere: npm 11.16.0 and later already show warnings up front, so you can prepare for the migration.
Auto-execution also brought benefits you never noticed
I've written "auto-execution is dangerous" up to here, so in fairness, here's the other side. Auto-running postinstall has long quietly powered conveniences engineers enjoyed without ever thinking about them. In fact, the reason "everything just works after npm install" is precisely this mechanism.
What auto-execution powered
Examples
Building native modules
better-sqlite3 / bcrypt compiling C/C++ for your environment
Downloading binaries and browsers
esbuild / Cypress / Puppeteer fetching executables and browsers
Transpiler post-processing
@swc/core / core-js initializing via postinstall
So what happens to the specific libraries you use? I checked each package's install-time scripts directly against the npm registry and summarized the impact below (verified against the latest versions at the time of writing).
Popular library
What it does at install time
What breaks if you don't allow it
esbuild
postinstall fetches a platform-specific binary
esbuild itself won't run; many build tools such as Vite depend on it internally, so the impact is wide
better-sqlite3
install runs a node-gyp native build
the SQLite driver fails to load and DB access breaks
bcrypt
install runs a native build
password hashing won't work
Cypress
postinstall fetches the test-runner binary
E2E tests won't launch
Puppeteer
postinstall downloads Chromium itself
no browser to drive; scraping and testing become impossible
@swc/core
postinstall does native-related post-processing
builds and transpilation may be affected
core-js
postinstall runs post-processing (a message, etc.)
impact is minor; usually fine even if left unapproved
Conversely, the widely used Husky (a git-hooks manager) is on the less-affected side. Husky's hook setup runs not from a dependency but from the prepare script you write in your own package.json. v12 only blocks dependencies' scripts, so scripts you wrote in your own project keep running as before. Likewise, libraries such as sharp that recently dropped install-time scripts in favor of shipping prebuilt binaries are unaffected.
These ran silently behind npm install, and we got a "working environment" without even being aware they existed. From v12 on, even these legitimate, useful scripts need your approval the first time. In exchange for safety, a tiny bit of effort comes back. The shift is from "silently do everything" to "ask once whether it's OK" — and the only one inconvenienced by being asked is the attacker.
Try it on your own package.json: an impact checker
A cheat sheet alone doesn't answer "so what about my project," so here's a small checker. Paste your entire package.json and press "Check," and it surfaces the packages in your dependencies (and the like) that are likely to need approval in v12.
Everything runs entirely in your browser (nothing is sent anywhere). The verdict is a match against a list of known packages I actually verified against the npm registry.
Note: this checks only direct dependencies (those listed in package.json) against a known list. Deeper transitive dependencies, and minor or in-house packages not on the list, can't be judged here. For an accurate list, run npm approve-scripts --allow-scripts-pending on npm 11.16.0 or later.
What to do now, before v12 arrives
Rather than waiting for July, it's safer to get used to it now. The steps are simple.
-
1.
Move npm to 11.16.0 or later (
npm install -g npm@latest)
-
2.
Run
npm approve-scripts --allow-scripts-pending to list dependencies that ship scripts
-
3.
Review them and approve only the ones you trust with
npm approve-scripts
-
4.
Commit and share the updated
package.json
Pay special attention to CI (continuous integration). Projects that run npm ci in a clean environment may find that if you forget to commit the allowlist, native builds and binary downloads stop the moment you move to v12, breaking the build. Get allowScripts in order ahead of time and that day passes quietly.
Wrap-up: from "silently do it" to "ask just once"
After running it myself, the scariest part wasn't the attack itself but the silence: it only said "added 1 package," yet code had run. I had to admit I'd barely thought about what that command, which I type constantly, was doing behind the scenes.
The v12 change trades away a little convenience to erase that silence. The benefits we used to get automatically now have to be acknowledged once, as an "approval." It's more effort, but the only one inconvenienced by that effort is the attacker. For one of the most-typed commands in the world, npm install, it feels like a reasonable landing spot. Before July arrives, give approve-scripts a try.
References
-
•
Upcoming breaking changes for npm v12 (GitHub Changelog)
-
•
[RFC] Make install scripts opt-in (npm/rfcs PR #868)
-
•
Preparing for npm v12: install scripts and non-registry sources become opt-in (GitHub Community Discussion #198547)
-
•
npm v12 makes install-time scripts opt-in by default (gihyo.jp, Japanese)
-
•
Test environment: npm 10.9.7 / Node.js v22.22.2 (the auto-execution of install-time scripts behaves the same through v11)