Software supply chain attacks have become a major concern in recent years, with high-profile incidents highlighting the risks of malicious code being injected into widely used software dependencies.

One common attack vector is through install scripts, which are scripts that run automatically during the installation of a package. Attackers can use these scripts to execute malicious code on the victim’s system without the user’s knowledge or consent.

Our previous blog on pinning dependencies to specific versions has explored how to mitigate these risks by ensuring that your project only uses specific, known versions of dependencies and delaying the installation of new versions.

However, even with pinned dependencies, there is still a risk that malicious code could be executed through install scripts. As a response to this threat, npm v12 introduces a number of new features designed to mitigate these attacks.

In this article, we’ll explore the new features in npm v12, how they work, and what they mean for developers and security teams.


npm v12 Cheatsheet for Existing Projects

  1. Upgrade to npm v11.16.0 or later.
  2. Run npm install to identify any scripts that would be blocked.
  3. Use npm approve-scripts --allow-scripts-pending to list the packages with pending scripts.
  4. Use npm approve-scripts --all to allow all currently pending scripts.
  5. Commit the updated package.json file to your repository.
  6. Review the approved scripts and use npm deny-scripts to revoke approval for any scripts that you want to block.

Once you’ve completed these steps, you should be ready to start using npm v12 in your projects without impacting your CICD pipelines.


Breaking Changes in npm v12

The release of npm v12 in July brings three major changes to the defaults of how npm install works:

  • Install scripts are disabled by default. This means that preinstall, install, and postinstall scripts from dependencies will no longer run unless explicitly allowed.
  • --allow-git defaults to none, which prevents npm install dependencies resolving from git repositories (whether they’re direct or transitive). This feature was available since npm 11.10.
  • --allow-remote defaults to none, which prevents npm install dependencies resolving from remote URLs. This feature has been available since npm 11.15.

Install Scripts Disabled by Default

From npm v12 onwards, npm install will no longer automatically execute the preinstall, install, and postinstall scripts defined in a package’s package.json file unless they are pre-approved.

This is a significant change because these scripts have been a common vector for supply chain attacks. Malicious actors could inject harmful code into these scripts, which would then execute during the installation process, compromising the system.

What this means for developers is that they need to explicitly approve the execution of these scripts as part of their package management workflow going forward. The recommendation from GitHub is to upgrade npm to v11.16.0 to have access to the latest commands that will be required to manage these new features.

To find out what would be blocked in your current project, run:

# Identify what scripts would be blocked/skipped
npm install

# List the packages for the scripts that aren't approved
npm approve-scripts --allow-scripts-pending

The recommended approach by GitHub for migrating existing projects is to allow everything currently in the dependency tree first and then review and manually approve any scripts that are blocked.

# Allow everything that is currently pending
npm approve-scripts --all

Once the package.json file has been updated with the approved scripts, commit the package file to your repository. You can then later use the npm deny-scripts command to revoke approval for any scripts that you want to block.

Native Install Scripts

Any native dependencies that are installed via npm install will also be affected by this change, such as the sharp, bcrypt, node-sass packages that rely on node-gyp to compile native code during installation.

To allow these scripts to run, you’ll need to use the --allow-scripts flag or the allow-scripts configuration option.

# Allow scripts for a specific package / packages
npm install --allow-scripts=sharp,bcrypt

Global Install Scripts

These changes to the script execution behaviour also apply to global package installations. However, the approve/deny commands will cause an error for global installs as they only work inside a project with a package.json file.

The recommendation for global installs is to use the allow-scripts configuration option, or the --alow-scripts=<package> input flag.

# Allow scripts for a specific package
npm install -g --allow-scripts=<package>

# Allow scripts for specific packages globally
npm config set allow-scripts=<package>,<package>

Git Dependencies Now Require --allow-git

Previously, npm would resolve Git-based dependencies (e.g. a package pointing to a GitHub repo). From v12, Git dependencies are blocked by default unless you pass --allow-git explicitly.

This change mitigates a lesser-known code execution path where a malicious Git dependency could override the Git executable via .npmrc, even when --ignore-scripts was set. This change has been available since npm 11.10.0 and was first announced back in February 2026 .


Remote URL Dependencies Now Require --allow-remote

Similar to the Git dependency resolution, any dependencies resolved from remote URLs (such as direct HTTPS tarballs) will be blocked by default.

You’ll need to opt in to enabling URL-based resolution with --allow-remote going forward. This flag has been available since npm 11.15.0.


References