pnpm vs npm Security in 2026: Install Scripts and Release Age, Tested
I installed a harmless postinstall canary and a 17-hour-old release with npm 11, npm 12 and pnpm 12. Here is what each blocks by default and the settings to change.

On this page
Most npm malware runs the moment you install it, through a postinstall script, and it usually ships as a fresh patch release that gets pulled within hours or days. So the two package-manager settings that matter most for supply chain security are: does it run dependency install scripts by default, and will it install a version that was published an hour ago. I tested both on pnpm vs npm tonight with a harmless canary package and a real package that had just shipped a release. The defaults are not what most people assume, and they differ between npm 11 and npm 12.
pnpm vs npm security: results with default settings#
Tested November 3, 2026 on Linux with Node 26.13.0.
Node 24.22.2 bundles npm 11.19.1, which has the same script settings as 11.20. If you use the npm that came with Node, you're on npm 11.
Test 1: does the install script run?#
I made a local package whose only job is to write a file when its postinstall runs:
{
"name": "install-canary",
"version": "1.0.0",
"scripts": {
"postinstall": "node -e \"require('fs').writeFileSync('/tmp/canary','ran')\""
}
}Then I packed it with npm pack and installed the tarball into an empty project with each tool, deleting /tmp/canary before each run.
npm 11.20.0 printed a warning and then ran the script anyway. /tmp/canary existed afterwards:
npm warn install-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn install-scripts install-canary@1.0.0 (postinstall: node -e "...")
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.npm 12.2.1 printed a similar warning, but worded as "had install scripts blocked", and the canary file was not created.
pnpm 12.16.1 didn't run the script and exited with code 1:
× adding a new package
╰─▶ Ignored build scripts: install-canary@file:../canary/install-canary-1.0.0.tgz
help: Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts.The packages were still written to node_modules, but the failing exit code means CI stops instead of quietly continuing. pnpm's docs call this strictDepBuilds, and it's on by default.
I got the same pattern with esbuild@0.28.2, a real package with a legitimate postinstall: npm 11 ran it with a warning, npm 12 blocked it, and pnpm blocked it and failed.
Allowing the scripts you trust#
With npm (11.19+ and 12), approve per package. The approval is pinned to the exact version:
npm approve-scripts esbuild
node -p "JSON.stringify(require('./package.json').allowScripts)"
# {"esbuild@0.28.2":true}Because the approval is pinned, a new esbuild release will be flagged again, which is exactly when you want to look.
With pnpm, list trusted packages in pnpm-workspace.yaml:
allowBuilds:
esbuild: trueWith that file in place, pnpm add esbuild@0.28.2 finished with exit code 0. pnpm approve-builds gives you an interactive picker for the same thing.
Test 2: will it install a brand-new release?#
The ai package published 7.0.162 at 01:19 UTC on November 3, about 17 hours before I ran this. The previous releases were 7.0.161 and 7.0.160 on November 2, and 7.0.159 on October 30.
Both npm versions installed the newest release by default. pnpm skipped everything younger than 24 hours because minimumReleaseAge defaults to 1440 minutes, as described in pnpm's supply chain security docs. npm's min-release-age does the same once you set it, but it's measured in days and is off by default.
Note that the cooldown cuts both ways. A real security fix published today also waits. Both tools let you exempt packages: min-release-age-exclude in npm, minimumReleaseAgeExclude in pnpm. With minimumReleaseAgeExclude: [ai] in pnpm-workspace.yaml, pnpm installed 7.0.162 straight away.
Recommended settings for each#
npm#
In the project's .npmrc:
min-release-age=3
strict-allow-scripts=trueThen approve the few packages that genuinely need install scripts with npm approve-scripts <pkg>, and commit the allowScripts field in package.json. If you're still on npm 11, upgrading to npm 12 gets you blocking by default: npm i -g npm@12.
pnpm#
In pnpm-workspace.yaml:
minimumReleaseAge: 4320 # 3 days, in minutes
trustPolicy: no-downgrade # refuse a version with weaker publish provenance than earlier ones
allowBuilds:
esbuild: true
sharp: trueScript blocking, strictDepBuilds and blockExoticSubdeps (no git or tarball URLs deep in the tree) are already on by default.
Both#
- Commit the lockfile and install with
npm ci/pnpm install --frozen-lockfilein CI. The release-age window matters when versions get resolved, which is when the lockfile changes, so review lockfile diffs in pull requests. - Don't run installs with secrets in the environment. If a script does slip through, it can read whatever env vars the install step has.
- Build Docker images from the lockfile. My Next.js Dockerfile installs from the lockfile in a separate deps stage.
If you write server code that fetches user-supplied URLs, SSRF protection in Node.js covers the other half of keeping a Node app safe.
So, is pnpm more secure than npm?#
With default settings, yes, in both tests: pnpm blocked the install script and failed the install, and it refused a 17-hour-old release. npm 12 now blocks install scripts by default too, but still installs brand-new releases unless you set min-release-age. npm 11, which is what ships with Node 24 and Node 26 today, only warns about install scripts. Configured properly, npm 11.19+ and npm 12 can match pnpm on both points. The difference is what you get if you never touch the config.
Questions
Share


