GitHub announced a set of “breaking changes” for npm version 12 on 9 June 2026, estimated at the time for July 2026, to disable npm install scripts by default. The headline change is that npm install will no longer automatically execute lifecycle scripts (like preinstall, install, and postinstall) from your dependencies unless you explicitly approve them. GitHub describes these install-time scripts as the single largest code-execution surface in the npm ecosystem, since a single compromised package buried deep in your dependency tree can currently run arbitrary code on your machine or your CI runner the moment you run npm install.
Updated October 2026: npm 12 was estimated for July 2026 and has since been reported as released, so this post now covers the checklist for your CI today instead of a future change.
Quick answer: GitHub is changing npm so that it will disable npm install scripts by default. From npm 12, preinstall, install and postinstall scripts from your dependencies only run if your project has explicitly allowed them in an allowScripts block in package.json. The fix for your pipeline is to run npm approve-scripts locally, commit the result, and use npm ci --strict-allow-scripts in CI. The full checklist is below.
To understand why this matters so much, it helps to step back and look at how pipelines work, how GitHub Actions fits into that picture, and how the long-running confusion between npm install and npm ci ties directly into this security story.
What is a pipeline?
A pipeline (often called a CI/CD pipeline, for Continuous Integration / Continuous Deployment) is an automated sequence of steps that takes your code from “just written” to “tested, built, and deployed” without a human manually running each command. A typical pipeline might:
- Check out your repository’s code
- Install dependencies
- Run linters and automated tests
- Build the application (compile, bundle, transpile)
- Deploy the result to a server, container registry, or hosting platform
The point of a pipeline is consistency and speed. Every commit or pull request triggers the exact same sequence of steps, on a clean machine, so you catch problems early and avoid “it works on my machine” surprises.
How pipelines work with GitHub Actions
GitHub Actions is GitHub’s built-in automation platform for running pipelines. You define a workflow as a YAML file inside a .github/workflows folder in your repository. A workflow is triggered by an event, such as a push, a pull request, or a scheduled time.
Each workflow is made up of one or more jobs, and each job runs on a “runner,” which is a fresh virtual machine spun up specifically for that job. Inside a job, you define a series of steps, which are individual commands or reusable “actions” published by GitHub or the community. A very common Node.js workflow looks something like this:
- Check out the repository code
- Set up the correct version of Node.js
- Install dependencies, typically with npm
- Run the test suite
- Build and optionally deploy the project
That third step, installing dependencies, is exactly where this new security change lands, because it’s the step that runs npm install or npm ci, pulling in not just your direct dependencies but everything they depend on too.
npm install vs npm ci: what’s the difference?
Both commands install your project’s dependencies, but they behave quite differently:
npm install reads your package.json, resolves version ranges (like ^1.2.3), and will update your package-lock.json if the resolved versions have changed. It’s flexible and is the right tool for day-to-day development when you’re adding or updating packages.
npm ci requires an existing package-lock.json and installs exactly the versions specified there, with no resolution or updates. It first deletes node_modules entirely for a guaranteed clean install, and if package.json and the lock file are out of sync, it errors out rather than trying to reconcile them. This makes it faster and far more reproducible, which is why it’s the standard recommendation for CI/CD pipelines.
Crucially, both commands have historically run lifecycle scripts from every package in the dependency tree by default. That’s the behaviour npm 12 is changing.
How this change helps stop supply chain attacks
A “software supply chain attack” happens when an attacker compromises a package somewhere in your dependency tree, then has malicious code run automatically when developers or CI systems install it. Lifecycle scripts are the perfect vehicle for this: a package can define a postinstall script that, say, exfiltrates environment variables, downloads a second-stage payload, or modifies other files on disk, all without the developer ever running that code directly.
The changes coming in npm 12 close off several of these paths:
npm installwill no longer runpreinstall,install, orpostinstallscripts from dependencies unless they’re explicitly allowed for that project, including implicitnode-gyprebuilds for native packages.- Git dependencies (direct or transitive) will no longer be resolved unless explicitly allowed with
--allow-git. - Dependencies fetched from remote URLs, such as tarballs over HTTPS, will no longer be resolved unless explicitly allowed with
--allow-remote. preparescripts from git, file, and link dependencies are blocked in the same way.
This also closes a sneaky bypass where a Git dependency’s own .npmrc file could override which Git executable npm uses, even when the --ignore-scripts flag was set.
What developers should do now
GitHub recommended upgrading to npm 11.16.0 or newer ahead of the npm 12 release, then running a normal install and reviewing the warnings it produces about packages with scripts. From there, you can run npm approve-scripts --allow-scripts-pending to review which packages have scripts, approve the ones you genuinely trust, and commit the updated package.json. Once you’re on npm 12, only the scripts you’ve explicitly approved will continue to run, and anything unapproved simply won’t execute.
For teams relying on GitHub Actions, this is worth testing in a branch before it lands, since a CI pipeline that depends on an unapproved postinstall script (for example, one that builds a native binary) could suddenly fail once the new defaults take effect. The safest approach is to audit your dependency tree now, understand which packages genuinely need install-time scripts, and approve only those.
CI checklist: what to change today
- Upgrade to npm 11.16.0 or newer so that installs print warnings about anything npm 12 will block, then run your normal install and read them.
- Run
npm approve-scripts --allow-scripts-pending. This lists the packages whose install scripts are not yet covered byallowScripts, without changingpackage.json. - Approve the packages you trust with
npm approve-scripts <package>(ornpm approve-scripts --allto approve everything currently pending, which you should only do after reviewing the list), and block the rest withnpm deny-scripts. - Commit the updated
package.json. Approvals are pinned to the exact installed version by default (an entry looks likeesbuild@0.24.2), so re-runnpm approve-scriptsafter you update a package. - Add the
--strict-allow-scriptsflag in CI, for examplenpm ci --strict-allow-scripts. According to the npm docs, this turns the unreviewed-scripts warning into a hard error, so a new dependency with an unapproved install script fails the build instead of being silently skipped. - Audit Git and remote-URL dependencies. They are blocked unless you pass
--allow-gitor--allow-remote(available from npm 11.10.0 and 11.15.0 respectively). - Watch for native packages. Packages with a
binding.gypfile, which trigger an implicitnode-gypbuild, are the most likely to break, so test on a branch first.
Remember that your own project’s scripts block, such as a build or test script, is unaffected; the change only targets lifecycle scripts from dependencies.
FAQ
Does this also affect npm ci?
Yes. As described above, both npm install and npm ci have historically run dependency lifecycle scripts, and both follow the new policy.
How do I allow a package’s install script?
Run npm approve-scripts <package> and commit the allowScripts entry it writes to package.json.
What if my build breaks after upgrading?
A dependency probably needs an install script that is not approved yet. Run npm approve-scripts --allow-scripts-pending to see the list, approve the ones you trust, and re-run the build.
Sources: GitHub changelog: upcoming breaking changes for npm v12 and the npm approve-scripts documentation.
