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.
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 shrinkwrapis removed andnpm-shrinkwrap.jsonis 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 adduserand thestarcommands are gone, andnpm initno longer defaults new packages to the ISC licence.- The JSON output of
npm view,npm packandnpm publishhas 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