npm 12 is now generally available, and according to the GitHub Changelog post announcing it on July 8, the release changes npm's defaults in a way maintainers have been asking for for years: install scripts are now disabled by default (`allowScripts` off), and dependencies resolved from arbitrary git repositories or remote tarball URLs are blocked outright (`--allow-git none`, `--allow-remote none`) unless explicitly permitted.
What flipped in version 12
Here's the behavior every npm user has quietly lived with for years: install a package, and its `preinstall`, `install`, or `postinstall` scripts just run. No prompt, no confirmation, arbitrary code execution the moment `npm install` finishes resolving, for every package in your tree and every package in theirs. That's the default npm 12 kills. Scripts don't run anymore unless you or your project have explicitly said they can, through the new `approve-scripts` and `deny-scripts` commands built for managing exceptions package by package.
{ "config": { "allowScripts": false, "allow-git": "none", "allow-remote": "none" }}
// Explicitly allow a specific package's install script:// npm config set allow-scripts.<package-name> true// or, per the new subcommands:// npm approve-scripts <package-name>// npm deny-scripts <package-name>Two other doors get shut in the same release. Pulling a dependency straight from a git repository, and pulling one from an arbitrary remote tarball URL, are both blocked by default now instead of going through the registry. Attackers have leaned on both of those paths for a long time precisely because they skip whatever scanning npm applies to registry packages.
npm 11.16 started warning about exactly these patterns before 12 actually flipped the switch, which gave maintainers and CI pipelines a window to see what would break before it actually broke. Silently disabling functionality that a meaningful chunk of the ecosystem depends on, even functionality that's also an attacker's favorite tool, tends to go badly if you don't warn people first. Staging the rollout through a warning release was the right call.
I still remember the first time I watched a postinstall script phone home during what I thought was a routine dependency bump. After you've seen that once, the old default feels almost rude. Npm 12 is late to this party, and also the first time the largest JavaScript package manager has been willing to inconvenience the whole ecosystem over it.
Why the default finally moved
JFrog's security research puts a number on why npm bothered changing ecosystem-wide defaults instead of leaving this to individual project configuration: roughly 53 percent of the malicious npm packages it has tracked used exactly these vectors, install scripts, git dependencies, remote tarballs, to get code running during installation rather than during actual use. More than half. That's not a niche attack pattern.
There's a second, smaller change from the same window: in early August, npm started blocking sensitive account operations, generating access tokens, for one, from bypassing two-factor authentication through the GitHub Actions token path. Same theme, different door: a mechanism that had let people escalate access without tripping the 2FA prompt that's supposed to catch exactly that.
npm's own roadmap points toward OIDC-based publishing and staged or trusted publish flows next, with that work expected to keep developing into January 2027. What shipped in version 12 is being framed as one step in a longer sequence, fewer ways for a compromised or malicious package to run code on a developer's laptop or a CI runner without someone having explicitly, auditably allowed it.
Fifty-three percent is the kind of number that makes arguments about developer convenience sound thinner than they did five years ago. The packages that legitimately need install scripts were always a minority. The attack surface was the majority case among malware. Flipping the default was overdue. The warning release in 11.16 was how npm avoided making the overdue change into a surprise outage.
What breaks, and what you actually do
Plenty of projects will feel nothing. The packages that need install scripts, native binary builds, a handful of compiled dependencies, are a known, small category, and you can allow them explicitly with `npm approve-scripts` or the `allowScripts` config shown above rather than opening the door for everything. If your CI has been quietly leaning on a git or remote-tarball dependency somewhere, that's the thing to go find now, before a build breaks on you unexpectedly, either move it to a registry-published package or opt back in deliberately with `--allow-git` or `--allow-remote`.
It's fair to ask why it took this long. Install scripts have been a known attack vector for years, documented in postmortem after postmortem of npm supply-chain incidents, and the fix, don't run arbitrary code without asking, isn't exactly novel engineering. Changing a default this fundamental risks breaking a nontrivial slice of the ecosystem that depends on scripts running silently, which is exactly why npm staged the rollout through a warning release first rather than flipping the switch cold.
The same install-script mechanism that lets attackers run arbitrary code is also what lets a lot of legitimate packages build native bindings the moment they're installed, without a developer having to run a separate manual step. Npm can't just delete the feature. What it did here is flip who has to opt in, instead of every package getting to run code by default, every package has to be explicitly trusted to run code, and the ones that need it get an explicit exception. Same capability, opposite default, smaller attack surface. Npm isn't the first package manager to wrestle with this. What makes the move notable is the scale. A default change here touches a larger share of the industry's dependency graph than a comparable change almost anywhere else would.
If your build breaks on day one of npm 12, resist the urge to flip `allowScripts` back on globally just to make CI green. Find the one or two packages that actually need a script, approve those, and leave the rest blocked. The whole point of the new default is that the allow list should be short enough to review in a pull request.
If you're moving a repo this week
If you're moving a repo to npm 12 this week, the order that has wasted the least of my time is: upgrade on a branch, run a clean install, read which packages complain about blocked scripts, approve only those, then hunt for any git or remote-tarball dependencies hiding in lockfiles. Most apps stop at the first two steps.
Monorepos and internal private packages are where the pain concentrates. A native addon used in one workspace can break installs for everyone if nobody owns the allow list. Put the approvals in version control where a reviewer can see them. Don't bury them in a developer's local npmrc.
The early-August 2FA change for token generation via GitHub Actions is smaller news next to the install-script default, but it's the same instinct: close accidental privilege paths that only exist because the old happy path was too convenient. Npm 12 won't end supply-chain attacks. It does retire the laziest half of them as a free gift of the previous defaults.
- Open Source
- TypeScript




