npm vs pnpm vs Yarn vs Bun: Who Actually Protects You From the Supply Chain?
8 min read
npm spent June announcing that it will finally stop running dependency install scripts by default. pnpm users read the news and shrugged. They have had it since early 2025. Yarn and Bun block those scripts out of the box too.
So here is the scoreboard npm left out of its announcement, and the exact settings that turn any of these four package managers into a hardened one. Because choosing a package manager stopped being a question about speed and disk space. It is now a security decision, and AI just turned the volume up on it. Coding agents add dependencies on their own, at machine speed, and run npm install inside loops no human is watching. The amount of unreviewed code entering projects has exploded at exactly the moment the ecosystem figured out how dangerous unreviewed code is.
This is the closing piece of a three-part series. If you are catching up: part one covered npm flipping its install-script default to deny, and part two covered how to decide which packages to trust when you cannot read their code. This one looks at the tools themselves.
The Two Defenses That Actually Matter
Strip away the marketing and supply-chain hardening in a package manager comes down to two controls:
- Default-deny on install scripts. Does the tool run
preinstall/install/postinstallfrom dependencies automatically, or does it block them until you approve? This shuts down the single largest code-execution surface in the ecosystem. - Release-age cooldown. Will the tool install a version that was published twenty minutes ago, or does it wait until a version has been public long enough for the community to catch a poisoned release? Most malicious versions are detected and pulled within hours, so a short delay filters out the smash-and-grab attacks.
A tool that does both, by default, is protecting you whether or not you ever read its docs. A tool that does neither leaves you exactly as exposed as npm has been for fifteen years. Here is where each one actually stands.
The Scoreboard
| Defense | npm | pnpm | Yarn (Berry) | Bun |
|---|---|---|---|---|
| Blocks install scripts by default | Only in v12 (~July 2026); v11 just warns | Yes, since v10 (early 2025) | Yes, since 4.14.0 | Yes, blocks postinstall by default |
| Cooldown available | Yes (min-release-age), since 11.10.0 | Yes, on by default (1 day) since v11 | Yes (npmMinimalAgeGate) | Yes (minimumReleaseAge) |
| Extra hardening | npm audit signatures provenance | Cooldown defaults, supply-chain docs | enableHardenedMode re-validates the lockfile | Curated default trusted list |
The headline is uncomfortable for npm: it is the last of the four major package managers to block install scripts by default, and the only one where, as of mid-2026, the protection is still a warning rather than the default. pnpm goes furthest, shipping the cooldown switched on out of the box.
How to Harden Each One
The defaults matter, but you can turn all four into a hardened setup today. A note on the cooldown values below: every tool measures the wait differently. npm takes days (7), pnpm takes minutes (10080), Bun takes seconds (604800), and Yarn takes a duration string ("1w"). They all mean roughly one week here. Mixing up the units is the most common mistake.
npm
The default flips in v12, but you can prepare now on npm 11.16.0+. Review and approve scripts:
npm approve-scripts --allow-scripts-pending # list packages with scripts
npm approve-scripts esbuild # approve the ones you trust
That writes an allowScripts allowlist into package.json. Add a cooldown through your npm config so new releases have to age before they install (npm added this natively in 11.10.0):
npm config set min-release-age 7 # days
Then verify your tree’s signatures and provenance in CI with npm audit signatures.
pnpm
pnpm is the strongest out of the box, but it is worth being explicit. Approve build scripts interactively:
pnpm approve-builds
Or declare the allowlist and tighten the cooldown in pnpm-workspace.yaml (the field is allowBuilds in v11, onlyBuiltDependencies in v10):
allowBuilds:
esbuild: true
minimumReleaseAge: 10080 # minutes; default is 1440 (one day) since v11
minimumReleaseAgeExclude:
- '@types/*'
Yarn (Berry)
Yarn 4.14.0+ disables third-party scripts by default. Turn on the age gate and hardened mode in .yarnrc.yml:
enableScripts: false
npmMinimalAgeGate: '1w' # duration string, e.g. one week
enableHardenedMode: true
Opt individual packages back in through dependenciesMeta in package.json:
"dependenciesMeta": {
"esbuild": { "built": true }
}
Bun
Bun blocks postinstall by default and only runs scripts for packages you trust. Add them in package.json:
"trustedDependencies": ["esbuild"]
And set the cooldown in bunfig.toml, remembering this one is in seconds:
[install]
minimumReleaseAge = 604800 # seconds (7 days)
minimumReleaseAgeExcludes = ["typescript"]
AI Pours Fuel on Both Ends
AI makes this worse from two directions at once. It lowers the cost of building the attack, and it removes the human who used to catch it.
On the attacker’s side, exploits got cheap. Writing a convincing malicious package used to take real skill. Now a language model will write the obfuscated postinstall, generate a believable README and a plausible commit history, and mass-produce a hundred typosquatted variants of popular packages in an afternoon. The skill barrier that kept low-effort actors out of supply-chain attacks has mostly collapsed. There is even a purpose-built version of this: LLMs reliably invent package names that do not exist, attackers mine those popular hallucinations, register the exact names, and wait for someone’s agent to confidently install them. The industry already has a word for it, slopsquatting.
On your side, the reviewer left the room. The defenses from the rest of this series assume a human is paying attention. Increasingly, none is.
- Nobody reviews the new dependency. When an agent decides it needs a date library and runs
npm install, no human reads the package name, checks the download count, or notices it was published last Tuesday. The trust checks worth making never happen, because nobody is there to make them. - Scripts run with the agent’s keys. An autonomous agent’s environment often holds tokens, cloud credentials, and repo access. A malicious
postinstallfiring there is not a developer laptop you can wipe. It is a live key ring. - Machine speed, zero human review. Agents generate and install faster than anyone can audit, which is exactly the condition default-deny and cooldowns exist to survive.
Put the two together and it is a perfect storm: cheaper, more convincing attacks arriving faster, aimed at a pipeline where the human checkpoint has been automated away. This is why “security decision” is not an overstatement. The package manager is the one guardrail that still gets a vote after the human steps out of the loop. A tool that blocks scripts and ages releases by default keeps that guardrail up even when nobody is watching the agent work.
So Should You Switch?
Not necessarily. Be honest about what a switch buys you versus what it costs.
If you are starting a new project, pick on the merits. pnpm and Bun both give you default-deny plus a cooldown with almost no setup, and they are fast. Choosing one of them is a free security upgrade over a fresh npm project, at least until v12 lands.
If you are on an existing npm project, the math is different. Migrating a real codebase to a new package manager touches your lockfile, your CI, your Docker images, your contributors’ machines, and every script that shells out to npm. That is not free. And once npm v12 ships, the single biggest gap, default-deny on install scripts, closes on its own. Add a minimumReleaseAge and run npm audit signatures, and an npm project is in genuinely good shape.
The case for switching anyway is real when you want the protection on by default rather than dependent on someone remembering to configure it, when you run a monorepo where pnpm’s or Bun’s speed and strictness pay off daily, or when you are in a security-sensitive context where pnpm’s cooldown-on-by-default is exactly the posture you want to enforce across a team.
In other words: switch for the defaults and the developer experience, not because npm is about to leave you defenseless. After v12, it is not.
The Bottom Line
For a decade, the package-manager argument was about speed and disk space. It is now about whether your tool runs untrusted code by default and whether it waits before installing a release the world has not vetted yet.
By that measure, pnpm, Yarn, and Bun got there first, and pnpm got there hardest. npm is catching up, and after v12 the gap is mostly about defaults rather than capability. Whichever one you use, the move is the same: turn on default-deny, add a cooldown, and stop treating your package manager as a neutral downloader. It is a security control now, and with AI agents installing on your behalf, it is the last one standing between an agent and your credentials. Configure it like one.
And because no guardrail catches everything, prevention at install time only works if you can also see what is already vulnerable across the repos you maintain. That visibility problem, scanning a whole portfolio and tracking the fixes without your code ever leaving your network, is the one I built TedGuard to solve.