npm Install Scripts Security: Block Malicious Postinstall

How npm install scripts became the main npm malware delivery path, what npm v12 blocks by default, and how to allowlist the few packages that need them.

npm install scripts security: what npm v12 changes

Short answer: npm install scripts security is about controlling the shell commands (preinstall, install, postinstall, prepare) that packages can run during npm install. For years these scripts ran automatically with your full user privileges, which made them the delivery mechanism for most npm supply-chain malware. Since npm v12 (July 2026), dependency lifecycle scripts are blocked by default and must be explicitly allowlisted — but older npm versions, unpinned CI images, and runtime malware still leave real gaps to close.

What are npm install scripts?

npm install scripts — formally lifecycle scripts — are hooks a package declares in its package.json that npm executes at specific points during installation:

  • preinstall — runs before the package is installed
  • install — runs during installation
  • postinstall — runs after the package and its dependencies are installed
  • prepare — runs on local installs and before publishing

These run as ordinary shell commands with the full privileges of whoever invoked npm install — a developer laptop with SSH keys and cloud credentials, or a CI runner with deployment tokens. Any package in your dependency tree can define them, including transitive dependencies you never chose. A package doesn't even need an explicit script: shipping a binding.gyp file triggers an implicit node-gyp rebuild, which is also arbitrary code execution.

Legitimate uses exist. Native addons like bcrypt or sqlite3 compile C++ locally, and tools like Puppeteer download a browser binary after install. But they are the minority: academic analysis of the npm registry found only about 2.2% of packages use install scripts at all.

Why are npm install scripts a security risk?

Because until mid-2026, npm ran them automatically, with no prompt and no review step. That made postinstall hooks the standard payload delivery mechanism in real attacks:

  • Nx "s1ngularity" (August 2025): a stolen publish token pushed malicious Nx versions whose postinstall script harvested GitHub tokens, npm credentials, SSH keys, and crypto wallets from developer machines.
  • Shai-Hulud worm (September 2025): self-propagating malware that injected a script payload, added a postinstall entry, republished compromised packages, and used stolen npm tokens to spread to further packages.
  • Axios compromise (March 2026): the malicious code never touched axios itself. Two backdoored releases added a phantom dependency, plain-crypto-js, that axios never imported; its postinstall script downloaded and executed platform-specific remote-access trojans, then removed the hook from disk to hinder forensics. The versions were live for roughly three hours, and detection depended on the community noticing rather than on any automated safeguard.

The structural problem is trust by default. The attack doesn't need you to install the malicious package directly — it only needs to appear somewhere in your transitive tree, and the install itself is the compromise. You never get a chance to review the code before it runs. That's what separates install-time attacks from runtime malware, and it's why the ecosystem's answer was to stop running these scripts automatically. For the broader picture of how packages get compromised in the first place, see our guide to malicious npm packages.

What changed in npm v12?

npm v12, released in July 2026, flipped three install-time defaults from "allowed" to "opt-in", as announced in the official npm v12 breaking-changes notice:

  • allowScripts defaults to off. npm install no longer executes preinstall, install, or postinstall scripts from dependencies unless you explicitly allow them. Implicit node-gyp builds are blocked too, and prepare scripts from git, file, and link dependencies are treated the same way.
  • --allow-git defaults to none. Git dependencies (direct or transitive) no longer resolve unless allowed. This closes a path where a git dependency's .npmrc could override the git executable — code execution that even --ignore-scripts couldn't prevent.
  • --allow-remote defaults to none. Dependencies fetched from remote URLs, such as https tarballs, no longer resolve unless allowed.

The workflow around the new default is an allowlist committed to your repo. npm approve-scripts --allow-scripts-pending shows which packages want to run scripts; npm approve-scripts approves the ones you trust and npm deny-scripts blocks the rest, writing the result to package.json. All of this is available behind warnings from npm 11.16.0 (May 2026), so teams can prepare before upgrading.

How do you secure npm install scripts?

Here is a practical hardening sequence that works whether or not you've reached npm v12 yet:

  1. Check what runs today. On npm 11.16.0+, run your normal install and read the warnings, or run npm approve-scripts --allow-scripts-pending to list every package requesting script execution. The list is usually shorter than you expect.
  2. Upgrade to npm v12 so dependency lifecycle scripts are blocked by default. On older versions, get the same effect with npm config set ignore-scripts true or npm install --ignore-scripts — just remember --ignore-scripts is all-or-nothing.
  3. Build a minimal allowlist. Approve only packages with a genuine build need (native addons, binary downloaders) via npm approve-scripts, or use @lavamoat/allow-scripts on older toolchains. Commit the allowlist and review changes to it like code.
  4. Pin the same policy in CI. Set ignore-scripts=true in the project .npmrc (or upgrade CI images to npm v12) so pipelines — which hold your most valuable tokens — never run unreviewed scripts. Pair it with npm ci and a committed lockfile.
  5. Watch for the attack signature. A new version of an existing dependency that suddenly adds a postinstall entry is the single most common marker of a compromised release. Treat it as a red flag in dependency-update reviews, alongside the checks in our guide on how to check if an npm package is safe.
  6. Add an install boundary for known-bad versions. Script blocking stops the delivery mechanism, but a flagged version can still ship malicious runtime code. A registry-level control that refuses to serve versions with malware advisories closes that layer — see how an npm registry firewall fits alongside script policies.

How do package managers compare on install scripts?

Package managerDefault behaviorAllowlist mechanism
npm ≤ 11Runs dependency lifecycle scripts automatically (warnings from 11.16.0)--ignore-scripts (all-or-nothing), @lavamoat/allow-scripts
npm 12+Blocks dependency lifecycle scripts, git deps, and remote-URL deps by defaultnpm approve-scripts / npm deny-scripts, committed to package.json
pnpm 10+ (Jan 2025)Blocks dependency build scripts by defaultpnpm approve-builds; escape hatch --dangerously-allow-all-builds
Yarn Berry (PnP)Build scripts controllable via enableScripts; PnP limits install-time executiondependenciesMeta[pkg].built: false per package
BunBlocks dependency lifecycle scripts by default (except a small built-in trusted list)trustedDependencies in package.json

The direction across the ecosystem is unanimous: install-time code execution is now opt-in everywhere.

What does script blocking not stop?

Blocking lifecycle scripts removes the most common delivery mechanism, not the malware itself. A compromised package can still export malicious code that executes the first time your app require()s it — at runtime, with the same privileges. Typosquats and dependency-confusion packages still install cleanly. So treat script policy as one layer:

  • Script allowlists stop unreviewed install-time execution.
  • Behavioral analysis tools (such as Socket or Aikido) inspect what package code actually does, which can catch novel, not-yet-reported threats.
  • Advisory-based install boundaries block versions already flagged as malicious. This is the layer InstallSafe provides: it acts as a registry firewall that checks every requested version against OSV.dev advisory data and refuses to serve flagged versions to developers, CI, and AI coding agents, delivering unmodified byte-for-byte tarballs for everything clean. It blocks known-bad versions at the install boundary; it does not detect zero-hour threats that no advisory covers yet, which is exactly why it belongs alongside — not instead of — script blocking and behavioral tools.

You can check any package or your full dependency tree against current malware advisories with the free InstallSafe scan — no signup required.

FAQ: npm install scripts security

Does npm still run postinstall scripts automatically?

No. Since npm v12 (July 2026), preinstall, install, and postinstall scripts from dependencies are blocked unless you allowlist them with npm approve-scripts. On npm 11.16.0–11.x they still run but emit warnings; on older versions they run silently.

What does --ignore-scripts do?

npm install --ignore-scripts skips all lifecycle scripts for that install, including your own project's scripts. It's a blunt but effective control for npm ≤ 11; npm v12's per-package allowlist is more precise.

Will blocking install scripts break my build?

Only for the small set of packages that genuinely need them — native addons that compile via node-gyp, and binary downloaders like Puppeteer or esbuild. Run npm approve-scripts --allow-scripts-pending to see exactly which packages are affected, approve those, and everything else keeps working.

Do pnpm, Yarn, and Bun run install scripts?

pnpm 10+ and Bun block dependency scripts by default and use allowlists (pnpm approve-builds, trustedDependencies). Yarn Berry can disable builds globally or per package. npm was the last major package manager to make blocking the default.

Does blocking install scripts stop all npm malware?

No. It stops install-time execution, which most historical attacks used, but malicious code can still run when your application imports the package. Layer script blocking with dependency review, behavioral analysis, and an advisory-based install boundary that blocks known-malicious versions.

How do I audit which of my dependencies use install scripts?

On npm 11.16.0+, npm approve-scripts --allow-scripts-pending lists every pending script request in your tree. Expect a short list — only around 2% of npm packages declare install scripts.