npm, the package manager maintained by GitHub and shipped with Node.js, has released npm 12, shipping with a set of security related default changes to npm install alongside the deprecation of granular access tokens that bypass two factor authentication.
npm 12 turns three install behaviours that previously ran automatically into ones developers explicitly opt into. The changes were first announced in June and have been available behind warnings since npm 11.16.0, so teams could prepare before upgrading.
The headline change is that allowScripts now defaults to off. Dependency preinstall, install and postinstall scripts no longer run unless they are explicitly allowed in a project, and that includes implicit node-gyp builds for any package containing a binding.gyp file, even where no install script is declared. prepare scripts from git, file and link dependencies are blocked in the same way. Developers review what is pending, approve what they trust, and commit the resulting allowlist in package.json.
The other two defaults target non registry sources. --allow-git now defaults to none, closing a code execution path where a Git dependency's own .npmrc could override the Git executable even with --ignore-scripts set, and --allow-remote defaults to none, blocking https tarball dependencies. The related --allow-file and --allow-directory flags are unchanged.
GitHub's migration discussion recommends allowing what is already in the tree first, then tightening, and documents recipes for native modules, Cypress, Playwright, Puppeteer, Electron and Husky. Global installs and npx cannot use approve-scripts and need the config instead, for example npm config set allow-scripts=canvas,sharp --location=user. Full details are in the docs for npm approve-scripts, npm deny-scripts and allow-scripts config. Commenters on the thread flagged that an existing ignore-scripts=true takes precedence and silently defeats the allowlist, and that newly installed packages create a chicken and egg problem, since npm approve-scripts reads from node_modules and errors with ENOMATCH if the package is not yet installed.
Community reaction has been broadly supportive. The Hacker News thread drew 484 points and more than 200 comments, with atraac writing that:
postinstall scripts should've been removed long time ago, it's the cancer of NPM packages. There's so many deeply nested, uncontrolled postinstalls that run randomly when you pull something it's insane, I don't know how someone at some point ever though that was a good idea.
Others were less convinced. gear54rus pointed to legitimate use cases such as patch-package, while cookiengineer argued the allowlist ‘has no scope’ and leaves it ‘unpredictable if any of your dependencies of dependencies needs a script’.
Security researchers have raised approval fatigue as the bigger risk. Writing on OpenSourceMalware, one analyst noted that esbuild, sharp, core-js, puppeteer and bcrypt all rely on lifecycle scripts, and warned that repeated broken builds will turn deny by default into ‘a click-through prompt’, while pushing attacker activity onto surfaces with less visibility. JFrog reports the three vectors were involved in roughly 53% of malicious npm attacks observed in the past year.
npm is also the last major package manager to adopt these controls. pnpm has offered install script allowlisting for years, and on cooldowns pnpm shipped minimumReleaseAge in 10.16, yarn added npmMinimalAgeGate in 4.10.0 and bun followed in 1.3, ahead of npm's own min-release-age in 11.10.0.
npm is the default package manager for Node.js and is maintained by GitHub. It hosts the largest software registry in the world, giving developers a command line client and registry to install, share and publish reusable JavaScript and TypeScript packages. As a foundational part of the wider Node.js ecosystem, npm is used across frontend and backend projects to manage dependencies, run scripts and distribute open-source code at scale.