npm just disabled the feature attackers loved most, after 16 years
For most of its life, the world's biggest code registry ran strangers' scripts on your computer the moment you typed 'npm install'. After a brutal year of supply-chain attacks, npm v12 finally turns that off by default. Here is what changed, what did not, and what it means if you write software.

On 28 July 2026, GitHub announced the biggest security shake-up in npm's history. With the new npm v12, the registry that nearly every modern app depends on stops automatically running package "install scripts", the exact mechanism attackers have used to hijack developers' machines. It also blocks installing dependencies from raw git or remote URLs by default. Both are on by default now, after a year of painful supply-chain attacks (Shai-Hulud, the @asyncapi compromise, a wallet-stealing Injective package). Some credential and login upgrades are announced but still rolling out. The short version: npm is moving from "trust by default" to "deny by default", and it is overdue.
If you have never written code, this sounds like inside baseball. It is not. The software running your bank app, your favourite website and half the tools on your phone is built on open-source building blocks, and the single biggest warehouse of those blocks is npm. When that warehouse changes how it handles trust, the ripple reaches almost everything. And in July 2026 it changed in a big way, a shift GitHub laid out in full on 28 July.
For roughly 16 years, npm did something convenient and quietly dangerous: when you installed a package, it would automatically run little setup programs bundled inside it, no questions asked. That convenience became attackers' favourite doorway. npm v12 finally shuts it. Here is the honest breakdown.
What actually changed?
The headline change, from GitHub's announcement (GitHub owns npm), is blunt: "With npm v12, we are rolling out a breaking change that disables these install scripts by default."
Two things are now off unless you explicitly allow them:
- Install scripts. Packages can no longer automatically run their bundled setup code (the "preinstall" and "postinstall" steps) when you install them. If a package genuinely needs one, you now have to approve it.
- Git-URL and remote-URL dependencies. npm also "blocked [additional vectors] by disabling dependencies via git or remote URLs by default", closing another way to sneak code onto your machine at install time.
One crucial nuance the headlines blur: this is disabled, not removed. Install scripts still exist and still run for packages you approve; npm has simply flipped the default from "run everything" to "run nothing unless told". That distinction is the whole philosophy of the change.
Why did npm do this? A brutal year of attacks
This did not come out of nowhere. It is a direct response to a run of software supply-chain attacks that turned trusted packages into weapons, and security researchers documented each one.
- Shai-Hulud (around September 2025): a self-replicating "worm" that infected npm packages, stole developer and cloud credentials, then used them to republish itself into more packages. A later wave hit tens of thousands of code repositories.
- The @asyncapi compromise (14 July 2026): attackers poisoned several packages from a popular project whose main package alone pulls roughly two million downloads a week, planting a multi-stage payload that could steal secrets and spread further, as The Hacker News reported.
- Injective's "sdk-ts" (8 July 2026): a compromised version of a real crypto-development package was published that stole cryptocurrency wallet private keys and seed phrases from apps that used its wallet functions.
The common thread is how the attackers got in: a compromised maintainer account or code repository let them slip malicious code into a trusted package. What varied was when that code ran. Shai-Hulud executed automatically at install time, the exact behaviour npm v12 now blocks by default. The other two were sneakier: @asyncapi's payload ran when the package was imported into a project, and Injective's only when its wallet functions were used, so disabling install scripts does nothing to stop either once the package is on your machine.
That split explains why npm is doing more than one thing. What all three attacks shared was the way in, a hijacked account or repository, which is why npm is also tightening how packages get published and how credentials work. Install-time execution has long been the most common trigger for npm malware, so turning it off closes the single biggest and most immediate door. But as two of these three attacks show, attackers are already shifting to code that runs at import or at runtime, which this change does not touch.
What is an install script, and why was it so dangerous?
Here is the plain-English version. When you run npm install, you are downloading code written by strangers, sometimes hundreds of packages deep, most of which you have never heard of. An "install script" is a small program a package author can attach that runs the instant the package lands on your machine, before you have used it or even looked at it.
For legitimate uses (compiling a piece of native code for your system, say) this is handy. For an attacker who has slipped malicious code into a popular package, it is a gift: the moment a developer types npm install, the attacker's code runs with that developer's access, to their files, their saved passwords, their company's systems. The convenience and the danger were the same feature. npm v12 accepts that, at global scale, the danger finally outweighed the convenience.
What else is changing, and what is not yet
It is worth being precise here, because not everything announced is live, and honest coverage should say so.
Live now, alongside npm v12:
- Trusted publishing. Maintainers can publish packages straight from their automated build systems using short-lived, verified credentials (via a standard called OIDC), instead of long-lived passwords that can be stolen.
- Staged publishing. New package versions can be held in a staging state until extra approval and two-factor authentication are provided, adding a checkpoint before code goes public.
Announced but still rolling out (from GitHub's September 2025 plan, not yet fully in force):
- Access tokens with a much shorter, roughly seven-day lifetime.
- A move from app-based one-time codes to stronger FIDO / passkey two-factor authentication.
- Phasing out old-style "classic" access tokens.
If you read that a developer's entire login was overhauled overnight, that is not quite right yet; those pieces are being introduced gradually. What is unambiguously live today is the install-script and dependency default change, and that is the one that matters most.
What it means if you write software
For most developers, the practical effect is small but real. A handful of legitimate packages rely on install scripts, particularly ones that compile native components, and those may need you to explicitly approve them the first time. Expect a little friction, some npm install runs that ask for a decision instead of silently doing everything. That friction is the point.
The bigger picture is the honest one to sit with. For years, the strategy for securing open-source registries was mostly to detect bad packages after they shipped, scanning, flagging, removing. npm v12 is a quiet admission that after-the-fact detection cannot carry the load alone at this scale: there are too many packages, uploaded too fast, for scanning to catch everything. So the ecosystem is adding a safer default on top of it, deny first, allow deliberately. It is less convenient, and it is the correct trade. If you build anything on open source, and almost everyone does, this is the rare security change you should be glad broke your workflow a little. For more, see the Software section and our guide to the best AI coding tools that increasingly sit on top of this same ecosystem.
What npm v12 changes, at a glance
| Old default | New default (npm v12) | |
|---|---|---|
| Package install scripts | Run automatically on install | Disabled; run only for approved packages |
| Git-URL / remote-URL dependencies | Allowed | Disabled by default |
| Publishing from CI | Long-lived tokens | Trusted publishing via OIDC (live) |
| New version publishing | Instant | Optional staged publishing + 2FA (live) |
| Token lifetime / 2FA | Long-lived tokens, app-code 2FA | Short-lived tokens + FIDO 2FA (announced, rolling out) |


