npm 12 Stops Running Your Dependencies' Install Scripts by Default

The biggest change to npm install in years closes a favourite supply-chain foothold, and moves the cost onto every project that compiles native code.

3 min read ·

The npm team at GitHub published npm 12.0.0 on 8 July, and it changes what npm install does by default. Lifecycle scripts declared by dependencies (preinstall, install and postinstall) no longer run unless the root project's allowScripts policy permits them. Git dependencies and dependencies fetched from arbitrary tarball URLs are blocked too, with the new allow-git and allow-remote settings defaulting to none.

None of this should be a surprise. GitHub announced the plan on 9 June, and npm 11.16.0 and later already print warnings for installs that would break. But warnings in a CI log are easy to ignore, and a default change is not.

Why this matters

Install-time scripts have been the simplest way to turn a compromised package into code execution on developer laptops and build servers. An attacker who publishes a malicious version does not need anyone to import the package; the install is enough. Removing that by default takes away the cheapest version of the attack, and it does so for the whole ecosystem rather than only for teams that already knew to set ignore-scripts.

The git and remote-URL blocks matter as well. Dependencies pulled from a repository or a URL bypass the registry's publishing controls entirely, and GitHub's own changelog described the git case as a code-execution path. Making them opt-in turns an invisible risk into a visible line in your configuration.

What breaks

The cost lands on packages that legitimately need to do work at install time. The June changelog is explicit that native builds are caught: a package with a binding.gyp file and no explicit install script is still blocked, because npm would otherwise run an implicit node-gyp rebuild. Anything that compiles a native addon or downloads a platform binary during install will now arrive half-installed until someone approves it.

The shipped workflow, per the release notes, is to install, run npm install-scripts approve to record which packages may run scripts, then npm rebuild to execute the newly approved ones. The approvals live in the root package.json, which is the right place: they get reviewed and versioned like any other dependency decision. One practical caution is that GitHub's June preview post described the approval commands under different names from the ones in the 12.0.0 release notes, so check npm help rather than copying commands from older migration guides.

The release carries a long tail of other breaking changes that will catch scripts and tooling:

  • npm shrinkwrap is removed and npm-shrinkwrap.json is no longer honoured, either at the project root or inside published tarballs.
  • Unknown keys in .npmrc, unknown flags and abbreviated flags now throw errors instead of warnings. Old, half-forgotten config lines will start failing builds.
  • npm adduser and the star commands are gone, and npm init no longer defaults new packages to the ISC licence.
  • The JSON output of npm view, npm pack and npm publish has changed shape, which matters to any release automation that parses it.
  • Supported Node.js versions are now ^22.22.2 || ^24.15.0 || >=26.0.0.

The sceptic's view

I am broadly in favour, with two caveats. First, an allowlist is only as good as the review behind it. If the habit becomes "approve everything the first time CI goes red", the policy turns into a speed bump that attackers drive over by compromising an already-approved package. The control moves risk from "any dependency" to "approved dependencies", which is a real improvement, not a solution.

Second, the change only protects people running npm 12. Which Node.js releases bundle it is a separate question that the release notes do not settle; they only state the supported engine range. Teams pinned to an older npm through their base image will keep the old behaviour without realising it. If you want the protection, pin the npm version in your images explicitly rather than inheriting whatever arrives with Node.

The sensible move this week is to run your install on npm 12 in a branch, see which packages are blocked, and decide deliberately which ones earn an approval. That list is the most honest inventory of install-time trust your project has ever had.


Sources

Responses (2)

Sign in to leave a response.

  • Agree on the rubber-stamp risk. The approval list should get the same code review as a lockfile change, or it will be approved by whoever is most annoyed at the red build.

  • The .npmrc change is the sleeper. Unknown keys now throw, and plenty of repos carry config lines nobody has read since 2019.

More from Daniel Okoye

Recommended from Horizon