Prepare a Node.js Project for npm v12 Install Security

John Burns

npm v12 changes what an ordinary dependency install is allowed to do. Its new defaults leave dependency lifecycle scripts disabled unless they are explicitly approved, and they refuse Git and remote-URL dependencies unless those dependency sources are explicitly allowed. That is a useful supply-chain boundary, but it can also make a familiar npm ci fail to produce a usable application when a native module, generated client, or other dependency normally prepares itself during installation.

This guide prepares a Node.js project for npm v12 without weakening the new defaults globally. The result is a reviewed project-level allowlist for the install scripts it genuinely needs, an inventory of non-registry dependency sources, and a CI test that proves a clean install and the project test command still work. The commands are documentation-verified examples, not results from a particular application. Make the change on a branch, and do not approve a package merely because a build failed.

What changed in npm v12

On 8 July 2026, npm v12 became the latest release. GitHub’s npm changelog says that allowScripts now defaults to off. As a result, dependency preinstall, install, and postinstall scripts, plus implicit node-gyp builds, do not run until the project explicitly permits them. The same release makes Git dependencies and dependencies fetched from remote URLs opt-in through --allow-git and --allow-remote.

These defaults distinguish a package download from executing code supplied by that package. They do not make a dependency trustworthy, and they do not replace lockfiles, code review, registry access controls, or vulnerability management. They do make the execution decision visible in the repository, which is useful when a developer laptop and a CI runner must make the same choice.

First, record the package-manager version used by the project and the Node.js version expected by its runtime or CI configuration:

node --version
npm --version
git status --short

Run the preparation in a clean worktree. An existing node_modules directory can hide a missing build step because it may contain files produced by an older installation. Keep the current lockfile under review as well. npm’s ci command is intended for clean, frozen dependency installation; if it reports that the lockfile and package.json disagree, fix that discrepancy deliberately instead of replacing the lockfile during an upgrade.

Find the scripts that the project owns

Start with the root package. Root lifecycle scripts are part of the project being checked out, whereas the v12 change is principally concerned with lifecycle code in dependencies. Knowing what the project runs makes it easier to distinguish an expected application build from an unexpected dependency action.

npm pkg get scripts
npm pkg get dependencies devDependencies optionalDependencies

Review prepare, preinstall, install, and postinstall entries particularly carefully. A root prepare step may generate source code or compile an asset. It should be documented and should run under the project’s normal test policy, but it is not a reason to approve every dependency script.

Also identify dependencies that do not come from the ordinary registry. Look at the declared package specifications and the resolved entries in the lockfile for git+, a Git host shorthand, file:, or an external tarball URL. Do not treat a Git dependency as interchangeable with a registry package simply because the name is familiar: its revision, access controls, and integrity evidence can be different.

grep -nE 'git\+|github:|gitlab:|bitbucket:|file:' package.json package-lock.json

The command is an inventory aid. It may return no lines even when a transitive dependency needs review, and a project using a different lockfile format needs an equivalent inspection. If the project needs a Git or remote dependency, identify why it exists, pin it according to the project’s policy, and keep the permission as narrow as the npm documentation allows. Do not set broad environment-wide allowances merely to make one checkout succeed.

Use npm 11.16 or later to review pending scripts

The npm team exposed the new behavior behind warnings in npm 11.16.0 and later so projects could prepare before v12 became the default. On a disposable branch or fresh clone, run the documented review command:

npm approve-scripts --allow-scripts-pending

npm presents pending dependency scripts for review and writes the approved project allowlist into package.json. Read each package name before accepting it. Native modules are common candidates because they compile or fetch platform-specific artifacts during installation, but “common” is not evidence that a particular package is appropriate for a particular project.

For each requested package, answer three questions before approval:

  1. Why does this project require the script?
  2. Which version and source does the lockfile select?
  3. Can the project build or test without it, or is the script required only for a supported platform?

A package that downloads a binary, invokes a compiler, generates code, or uses credentials deserves a closer look. Inspect its release history, repository, and install script rather than relying on a package name. If the dependency should not execute code at install time, leave it unapproved and investigate the dependency path. A failed native build can also indicate a missing compiler, incompatible Node.js version, or unsupported platform; adding an allowlist entry does not fix those conditions.

After review, inspect the source change instead of committing it automatically:

git diff -- package.json package-lock.json
git diff --check

The intended result is a small, intelligible package.json change that records only the dependencies whose scripts the project needs. A lockfile change may be appropriate if the normal package-manager workflow created one, but a broad dependency refresh during this migration makes review harder. Separate a lockfile refresh from the permission decision where possible.

Prove the clean install in CI conditions

A local incremental install is not enough. Remove only the project dependency directory in a disposable clone or a CI workspace, then run the same installation mode CI will use. Do not remove files from a working directory that contains uncommitted application work.

rm -rf node_modules
npm ci
npm test

npm ci should finish without silently relying on a previous build artifact. npm test is only an example; use the project’s documented verification command if it has a separate type check, lint, build, or integration-test step. For a web application, a production build is often the important confirmation because it exercises generated assets and native optional dependencies that a unit test may not load.

Add the same sequence to a non-production CI job before changing the main build image to npm v12. Pin or otherwise record the Node.js and npm versions during the transition so a runner image update cannot change the behavior without a reviewed pull request. Preserve the CI logs for an initially blocked script, but redact private registry URLs, tokens, and internal package names before attaching them to tickets or public discussions.

If CI uses dependency caching, include the npm version and lockfile digest in the cache key. A cache built with pre-v12 behavior can conceal the very script decision the migration is intended to test. Re-run the job from an empty cache at least once after the allowlist has been committed.

Keep the permission review small

The new default is most useful when its exceptions remain visible. Revisit the allowlist when dependencies change, especially after an upstream package switches from pure JavaScript to a native helper or introduces an install hook. Remove an entry when the package no longer needs it, and treat a new pending script as a dependency-review event rather than routine build noise.

npm v12 also changes the handling of 2FA-bypass granular access tokens on the npm registry. That is a related but separate publishing concern: do not solve install failures by adding registry publishing credentials to builds. GitHub recommends moving automated publishing toward trusted publishing or a staged process with human approval as the 2FA-bypass changes arrive.

With a committed script allowlist, a reviewed dependency-source inventory, and a clean CI installation, the project can move to npm v12 while retaining its intended build behavior. The next useful step is to make the review part of ordinary dependency updates, so the first appearance of new install-time code is visible in a pull request rather than discovered during a release.

Sources