npm 12 turns off dependency install scripts by default, and blocks git and remote-URL dependencies unless you allow them
npm 12 skips dependency install scripts and refuses git and URL dependencies unless allowed. Upgrade, run npm approve-scripts and commit the allowlist.
- Published
- Updated
- Fixed in
- npm 12.0.0 (8 July 2026); preview warnings from npm 11.16.0
- Exploitation
- Not applicable. This is a platform change that blocks the install-time step most npm malware relies on
What happened
GitHub announced the changes on 9 June 2026 and said they were already visible as warnings in npm 11.16.0 and later (GitHub changelog). npm 12.0.0 was published on 8 July 2026 (npm; The Hacker News). New defaults:
allowScriptsis off.preinstall,installandpostinstallscripts from dependencies do not run unless allowed. This includes the implicitnode-gyp rebuildnpm runs for a package with abinding.gypand no install script.preparescripts from git, file and link dependencies are blocked the same way.--allow-gitdefaults tonone. Git dependencies, direct or transitive, are not resolved. GitHub cites a code-execution path where a git dependency’s.npmrcoverrides the git executable even with--ignore-scripts.--allow-remotedefaults tonone. Dependencies from remote URLs, such as https tarballs, are not resolved.--allow-fileand--allow-directorykeep their old defaults.
Warning or error? Early reports disagreed. The npm 12 documentation settles it for scripts: installs “silently skip lifecycle scripts” for any dependency without a matching allowScripts entry, and end with a list of the packages whose scripts were skipped (npm docs). The install does not fail. A package that needed its script, such as a native addon, will then fail later, at run time.
The same release cycle also cuts what granular access tokens can do. GitHub plans to remove direct publishing from them in January 2027 and move automated publishing to trusted publishing (OIDC) or staged publishing (The Hacker News).
Am I affected?
This change protects you only once npm 12 is the version that actually runs npm install, on laptops and on CI images.
# Which npm is running here (12.x or later has the new defaults)
npm -v
# Dependencies whose install scripts would be (or are being) skipped
npm approve-scripts --allow-scripts-pending
# Packages in the lockfile that declare install scripts
grep -n '"hasInstallScript": true' package-lock.json
# Git and URL dependencies that npm 12 will refuse without --allow-git / --allow-remote
grep -nE '"(resolved|version)": "(git\+|git:|github:|https?://[^"]*\.tgz)' package-lock.json | grep -v 'registry.npmjs.org'npm approve-scripts needs npm 11.16.0 or later. If you pin an older npm in CI, for example through a Node.js base image or corepack, the old defaults still apply there.
What to do now
- Upgrade npm where installs run. CI images and developer machines. npm 12.2.1 is the
latesttag as of 2 November 2026 (npm). Its engines field requires Node.js^22.22.2 || ^24.15.0 || >=26.0.0. - Review pending scripts. Run
npm approve-scripts --allow-scripts-pending. For each package, decide whether it truly needs an install step. - Approve narrowly.
npm approve-scripts <pkg>writes a version-pinnedpkg@versionentry by default, so an update re-prompts. Avoid--no-allow-scripts-pin, which allows any future version, and avoid--all(npm docs). - Deny the rest explicitly with
npm deny-scripts <pkg>.approve-scriptsnever overrides afalseentry. - Commit
package.jsonso the allowlist applies on every machine and in CI (GitHub changelog). - Replace git and tarball dependencies with registry versions where you can. Where you cannot, pass
--allow-gitor--allow-remoteonly in the job that needs it. - For
npxand global installs, which have no projectpackage.json, use theallow-scriptsconfig, for examplenpm config set allow-scripts=canvas,sharp --location=user(npm docs).
Why this matters
Several attacks covered on this site ran from an install hook. The axios hijack used a postinstall script and the Shai-Hulud worm used preinstall. With npm 12 defaults, neither would have run on npm install. It does not help against payloads that run when your code imports the package, and a broad, unpinned allowlist entry gives the protection back. Keep the allowlist short and pinned.