Aller au contenu
medium

npm no longer runs install scripts: pnpm did it first

Lire cet article en français

9 min read

For sixteen years, npm install executed arbitrary code shipped by any of your dependencies. Not the dependency you picked: any of the hundreds it drags along behind it. The only defence was a flag, --ignore-scripts, that I have almost never seen enabled in production.

Since 8 July 2026 and the release of npm v12, that era is over. The preinstall, install and postinstall scripts of dependencies no longer run unless explicitly allowed. It closes the most profitable attack vector in the JavaScript ecosystem, the one used by Shai-Hulud, axios and the 57 Mastra packages.

I wanted to check whether other ecosystems were following the same path. The answer surprised me: npm did not lead, it caught up with its competitor. pnpm made that decision eighteen months earlier.

What npm v12 actually blocks

npm v12 refuses to run install scripts coming from your dependencies, but still runs your own. The distinction matters: your own build postinstall keeps working, it is third-party code that is neutralised.

The block is broader than the headline suggests. It covers three things:

  • preinstall, install and postinstall scripts of dependencies;
  • implicit node-gyp builds, triggered by the mere presence of a binding.gyp file with no declared script;
  • prepare scripts of dependencies installed from Git or a local link.

npm v12 also blocks Git dependencies and remote URL dependencies by default. The documented reason for Git dependencies is worth a detour: a malicious package could ship its own .npmrc pointing at a fake git executable.

To restore a legitimate script, npm provides a dedicated command that builds an allowlist you commit to the repository:

Fenêtre de terminal
npm approve-scripts

Why install scripts were the preferred vector

A postinstall script runs with the rights of the developer or the CI runner, with no validation, no review, and often without anyone knowing it exists. That is what makes it such an effective weapon: the victim did nothing beyond installing dependencies.

The incident record is eloquent. The Shai-Hulud worm compromised dozens of AntV packages by stealing runner secrets from GitHub Actions. axios shipped a remote access trojan. On 17 June 2026, 57 Mastra packages and more than 286 malicious versions went through the same door.

The long-standing recommendation, --ignore-scripts, has changed status: it is no longer advice, it is the default behaviour.

pnpm had an eighteen-month lead

Here is the fact that puts the announcement in perspective. I checked the publication dates directly in the npm registry, which timestamps them:

VersionPublishedWhat it brings
pnpm 10.0.07 January 2025Dependency scripts disabled by default
pnpm 11.0.028 April 202624-hour cooldown enabled by default
npm 12.0.08 July 2026Dependency scripts disabled by default

pnpm disabled install scripts eighteen months before npm, and went further: since pnpm 11, no version published less than 24 hours ago is installed, with no configuration required. Bun, for its part, only runs scripts from packages listed in trustedDependencies.

npm does offer an equivalent waiting period, min-release-age, shipped in February 2026. But it is disabled by default, which for this kind of protection amounts to not existing for the vast majority of projects.

PyPI has been setting the pace since 2023

The real metronome of registry hardening is PyPI, and that is rarely said. The order of dates leaves no ambiguity:

  • 2023: PyPI ships OIDC trusted publishing, the first major registry to do so. crates.io followed in July 2025, npm in late July 2025, NuGet in September 2025.
  • 1 January 2024: two-factor authentication becomes mandatory for every publishing account.
  • November 2024: Sigstore-signed digital attestations arrive.
  • 22 July 2026: a release refuses any new file after fourteen days, to prevent retroactive poisoning of old versions.

npm did in one year what PyPI built over three, but under duress: it is the only registry that revoked existing credentials en masse, deleting all classic tokens on 9 December 2025.

Other ecosystems do not have the same problem to solve

This is the trap of such a comparison, and I fell into it while preparing this piece: ranking ecosystems by virtue makes no sense when they do not share the same architecture. The execution model matters more than the date of the latest announcement.

EcosystemCode at install timeNature of the situation
npmBlocked since July 2026Recent fix
pnpm / BunBlocked since January 2025Early fix
Composer (PHP)Never run for dependenciesDesign choice
GoNever existedDesign choice
NuGetRemoved in 2017 with PackageReferenceLong-standing fix
Maven CentralNone, JARs are passiveDesign choice
RubyGemsStill open (native extensions)Genuine lag
PyPIPossible through a source archiveNeutralised in practice by wheels

Composer is instructive: its documentation states that only scripts defined in the root package run, and plugins require an allowlist. The problem npm just fixed never existed in PHP.

Go neither: modules are source code, go get executes nothing, and there are not even publishing accounts to phish since everything comes from repositories. Presenting that as an advantage earned in 2026 would be dishonest, it is an original design decision.

What these protections cost

None of these measures is free, and staying silent about it would paint a false picture.

Waiting periods also delay fixes. Waiting 24 hours or seven days before installing a new version protects you from the package compromised an hour ago, but delays the patch for a critical flaw by just as long. Hence the escape hatches each tool provides, which become weaknesses in turn: a package on the exclusion list drops the protection exactly where it mattered.

PyPI's fourteen-day window has a side effect: you can no longer retroactively add a wheel built for a new Python version to an old release. You have to republish. PyPI's own impact analysis puts the friction at 56 projects out of the 15,000 most downloaded, which is low but not zero.

Trusted publishing concentrates risk. It only works with the large CI platforms today. Maintainers publishing from elsewhere stay on tokens, and the security of the ecosystem couples itself to two providers.

Key takeaways

  • npm v12, released 8 July 2026, no longer runs dependency install scripts. The first break in that behaviour in sixteen years.
  • Only dependency scripts are blocked, yours keep running.
  • The block also covers implicit node-gyp builds, which will break projects with native modules in CI.
  • pnpm made that decision eighteen months earlier, in January 2025, and has enforced a 24-hour waiting period by default since April 2026.
  • npm has offered that delay since February 2026, but disabled by default.
  • PyPI has led since 2023: trusted publishing first, mandatory 2FA from January 2024, Sigstore attestations, releases frozen after fourteen days.
  • Composer, Go, Maven and NuGet do not have this problem, by design or for a long time. That is not a recent lead, it is a different architecture.
  • Waiting periods also delay security fixes: the protection has a cost you should accept knowingly.