Upgrading to pnpm 11: Postinstall Scripts, Supply Chain Security, and What You Need to Know
pnpm 11 stops running dependency build scripts by default. What changed, why it's a real security win, and how to audit what you whitelist.
If you’ve recently upgraded to pnpm 11 — or are planning to — you’ve likely encountered a new kind of warning during pnpm install:
[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.27.7, sharp@0.34.5, puppeteer@24.40.0 ...
This isn’t a bug. It’s pnpm 11 telling you it has deliberately stopped running code on your behalf. Here’s what that means, why it matters, and how to handle it correctly.
What Changed in pnpm 11
Build scripts are now ignored by default
The biggest security change in pnpm 11 is that postinstall scripts for dependencies no longer run automatically. In previous versions, any package you installed could execute arbitrary shell commands on your machine the moment pnpm install finished. pnpm 11 blocks this by default and requires you to explicitly opt in for each package you trust.
Node.js 22 is now required
pnpm 11 drops support for older Node.js versions and requires Node.js 22 or newer. If you’re upgrading pnpm, make sure your Dockerfiles, CI pipelines, and local environment are all aligned on Node 22.
--config.* CLI flags are removed
Previous versions allowed overriding config values inline via --config.key=value. This has been removed. Configuration now has to be explicit — either via direct CLI flags, environment variables, or .npmrc files. This makes build scripts more predictable and harder to accidentally misconfigure.
Lockfile format upgraded
The lockfile format bumped to version 9.0 (introduced in pnpm 9) is now strictly enforced. Any lockfile generated by an older pnpm version needs to be regenerated. If your project has multiple independent pnpm-lock.yaml files (e.g. in lambdas or task runners), each one needs to be regenerated separately.
What Is a Postinstall Script?
A postinstall script is a lifecycle hook in a package’s package.json that runs automatically after the package is installed:
{
"scripts": {
"postinstall": "node install.js"
}
}
The lifecycle order is: preinstall → install → postinstall. Package authors use postinstall for things that can only happen once the package’s own files are already on disk — downloading a platform-specific binary, compiling a native C/Rust addon, or generating code from a schema.
Common legitimate examples:
- esbuild — downloads the native binary for your OS and CPU architecture
- sharp — compiles native libvips bindings for image processing
- puppeteer — downloads a Chromium browser binary
- @sentry/cli — downloads the Sentry CLI binary
- protobufjs — generates JavaScript from
.protofiles
Why This Is a Security Issue
Running arbitrary scripts during install is one of the most effective supply chain attack vectors available. A compromised or malicious package can use postinstall to:
- Exfiltrate secrets from your environment variables
- Write files outside its own directory
- Make network requests to attacker-controlled servers
- Install persistent backdoors
This is not theoretical. The event-stream incident in 2018 is the most well-known example: a widely-used npm package was transferred to a malicious actor who added a postinstall script that specifically targeted Bitcoin wallet credentials. Millions of projects were affected before it was caught.
The risk is especially high for transitive dependencies — packages you didn’t explicitly install, but that were pulled in by a dependency of a dependency. These receive far less scrutiny than your direct dependencies.
How to Handle It: onlyBuiltDependencies
pnpm 11 introduces the onlyBuiltDependencies field in the pnpm section of package.json. It’s an explicit whitelist of packages you trust to run their build scripts:
{
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"sharp",
"puppeteer",
"@sentry/cli",
"protobufjs"
]
}
}
Only packages listed here will have their postinstall scripts executed. Everything else is silently skipped.
Important: not whitelisting a package does not cause pnpm install to fail. The install succeeds — but the package may be broken. Failures surface later: next build crashes because the esbuild binary is missing, or your app throws at runtime because a native addon was never compiled.
How to Verify a Package Is Safe to Whitelist
When you encounter an unfamiliar package in the ignored builds list, don’t whitelist it blindly. Here’s how to evaluate it:
1. Read the script
cat node_modules/<package>/package.json | grep postinstall
cat node_modules/<package>/<script-file>
A safe script is boring. It downloads a binary or compiles code. A suspicious script is obfuscated, base64-encoded, or constructs strings dynamically to hide URLs.
2. Check who owns it and when
Go to npmjs.com/package/<name>. Look at weekly download counts, publish dates, and — most importantly — recent ownership transfers. A package with 500 downloads that was recently transferred and suddenly has a postinstall is a red flag.
3. Trace why it’s in your tree
pnpm why <package-name>
This tells you which of your direct dependencies pulled it in. If it’s pulled in by something completely unrelated to what the package claims to do, investigate further.
4. Read the source
The npmjs.com page links to the GitHub repo. Check that the postinstall script in the published package matches what’s in the repo — discrepancies between source and published artifact are a major red flag.
5. Check for known issues
pnpm audit
Or run the package through socket.dev, which specifically analyses postinstall scripts, network access patterns, and ownership changes across the supply chain.
The decision rule: if after these steps you can’t answer “this script does X, and that makes obvious sense for what this package is” — don’t whitelist it.
Does Every package.json Need a Whitelist?
No. The onlyBuiltDependencies field only applies to the pnpm workspace it’s defined in. If you have independent pnpm projects — lambdas, task runners, microservices with their own pnpm-lock.yaml — each is isolated.
The practical approach is: run pnpm install in each project directory, check what the “Ignored build scripts” warning lists, and only add onlyBuiltDependencies if you have packages that actually need their postinstall to work. AWS SDK packages, TypeScript utilities, and most pure-JS libraries don’t — so many standalone projects won’t need a whitelist at all.
Summary
pnpm 11 makes a meaningful improvement to supply chain security by defaulting to distrust for postinstall scripts. The overhead is low — a one-time audit of which packages in your tree legitimately need to run code on install — and the protection is real. Think of onlyBuiltDependencies not as a chore, but as a documented record of exactly which packages you’ve consciously decided to trust with execution on your machine.