npm 12 will stop running install scripts from dependencies automatically, closing what GitHub calls the single largest code-execution surface in the ecosystem. Here's what changes, why it matters, and how to prepare before the release lands next month.
GitHub is about to change one of the most quietly dangerous behaviors in the JavaScript ecosystem. With npm version 12, scheduled for release next month, the npm install command will no longer run install scripts from your dependencies by default. The company is also tightening how npm resolves Git and remote URL dependencies. Together these count as "breaking changes," and they target a class of attack that has burned developers repeatedly over the past few years.

To understand why this matters, you need to understand what actually happens when you type npm install. Most developers think of it as a download step: fetch the packages listed in package.json, drop them into node_modules, done. But npm packages can register lifecycle hooks, scripts named preinstall, install, and postinstall, that execute automatically during installation. These hooks exist for legitimate reasons. A package with native C++ bindings needs to compile them for your platform, and that compilation happens through an install script. The problem is that the same mechanism runs for every package in your dependency tree, including the hundreds of transitive dependencies you never chose directly.
The attack surface in one command
GitHub described install-time lifecycle scripts as the "single largest code-execution surface in the npm ecosystem," and the description is hard to argue with. When npm install runs scripts from every transitive dependency, a single compromised package anywhere in that tree can run arbitrary code on a developer's machine or a CI runner. You do not have to import the package. You do not have to call any of its functions. Installing it is enough.
This is exactly the pattern behind a long run of supply chain incidents. An attacker compromises a popular package, or a tiny utility buried twelve levels deep in someone's dependency graph, and ships a malicious postinstall script that exfiltrates environment variables, steals cloud credentials, or plants a backdoor. Because CI systems run npm install constantly with broad permissions, they make especially attractive targets. The recent Miasma worm that spread across dozens of GitHub repositories worked on similar principles, abusing automatic execution to propagate.
The shift GitHub is making is a move from trust-by-default to approval-by-default. Instead of assuming every package in your tree should be allowed to execute code at install time, npm 12 assumes none of them should, unless you say otherwise. "Making script execution opt-in closes that path while keeping it one command away for the packages you trust," GitHub said.
What is actually changing
Three concrete behaviors change with version 12:
npm installwill no longer executepreinstall,install, orpostinstallscripts from dependencies unless they are explicitly allowed in the project.npm installwill no longer resolve Git dependencies, whether direct or transitive, unless you allow them with--allow-git.npm installwill no longer resolve dependencies from remote URLs, such as HTTPS tarballs, unless you allow them with--allow-remote.
The script-blocking change is broader than it first sounds. It also covers native node-gyp builds. A package that ships a binding.gyp file but no explicit install script still gets blocked, because npm runs an implicit node-gyp rebuild for it, and that counts as code execution. The prepare scripts from Git, file, and link dependencies are blocked the same way.
The Git change closes a subtle hole. By defaulting --allow-git to none, npm shuts down a code execution path where a Git dependency's .npmrc configuration file could override the Git executable itself. That path worked even with --ignore-scripts set, the flag developers have long reached for to suppress lifecycle scripts. In other words, the old defense was leakier than many people assumed, and the new defaults patch around it.
How to get ready
The practical guidance from GitHub is straightforward, and you can start now rather than waiting for the release. Upgrade to npm 11.16.0 or newer, run your normal install, and read the warnings npm prints. Those warnings tell you which of your dependencies actually use install scripts, which is usually a much shorter list than people expect.

From there, the workflow is built around a new command. Running npm approve-scripts --allow-scripts-pending shows you which packages have scripts pending approval. You review them, approve the ones you trust, and commit the updated package.json. "After that, only the scripts you approved keep running once you upgrade," GitHub explained. "Anything you leave unapproved will stop." The point is to do this auditing on your own schedule, with your eyes open, rather than being surprised when a build breaks the day version 12 ships.
This fits a pattern npm has been building toward. Earlier this year the registry introduced min-release-age, a setting that tells npm to reject any package version published less than a specified number of days ago. The reasoning there is that most malicious package versions get caught and pulled within hours or days of publication, so a short quarantine window filters out a meaningful share of attacks before they reach your machine. Pairing min-release-age with default script blocking gives you defense in depth: fewer freshly poisoned packages reach you, and the ones that do cannot execute code without your sign-off.
What this means for your projects
If you maintain applications, expect some friction during the transition, mostly from legitimate packages that genuinely need to compile native code. Database drivers, image processing libraries, and anything wrapping a native binary will likely show up in your approval list. The fix is not to disable the protection wholesale but to approve those specific, known packages and commit that decision so your whole team and your CI inherit it.
If you publish packages, this is a good moment to question whether your own package needs an install script at all. Many do not, and the ones that do can often move logic to runtime or to an explicit setup command. Reducing your reliance on automatic execution makes your package easier to trust and less likely to get left unapproved by cautious consumers.
The broader signal here is that the ecosystem's default posture is changing. For years the convenience of automatic install scripts outweighed the risk in most people's calculations, partly because the risk was invisible until something went wrong. GitHub is shifting that balance, making the safe choice the default and the convenient-but-risky choice an explicit, auditable opt-in. For developers who have watched supply chain attacks pile up, that trade is worth making, even at the cost of a one-time round of approvals. You can track the rollout and the full list of breaking changes through the npm documentation and the npm CLI repository as version 12 approaches.

Comments
Please log in or register to join the discussion