Why You Should Pick pnpm 11 Over npm in 2026

·Platform Decision·8 min read

Translated from the original Korean post. 한국어 원문 보기 →

Still on npm?

I was going through our company's projects the other day and noticed something: almost every team is still using npm as their default package manager. Which makes sense. npm ships with Node.js, every tutorial tells you to run npm install, and most days it just works. There's no obvious reason to switch.

Then pnpm 11 landed back in April, and the calculus changed. This isn't the old "pnpm is faster" performance argument. The gap now is on the security side, and it's hard to wave off. Less a tooling preference, more a question of how far up your supply chain you're willing to extend trust.

npm's structural problem

Swapping package managers always looks like low-priority work. "Nothing's on fire," you say, and it slides to the bottom of the sprint backlog. I've done exactly that. But in 2026, with supply chain attacks getting more sophisticated, this belongs in the infrastructure risk column.

The typical attack goes like this:

  1. A maintainer token for a popular package leaks
  2. The attacker publishes a patch version carrying malicious code
  3. CI/CD auto-updates because the range says ^1.2.0
  4. A postinstall script runs and exfiltrates env vars and secrets

Step 4 is the one that matters, because no human ever touches it. All that speed we bought with pipeline automation turns into the attacker's fastest path in.

Recently we've seen things like Mini Shai-Hulud, which infected npm, PyPI, and Packagist simultaneously, then pulled down the Bun runtime to execute an obfuscated credential stealer. Nobody's targeting one registry at a time anymore.

And npm has essentially no baseline defense against any of this. A package is installable the second it's published, transitive dependencies can come from anywhere, and every postinstall script runs by default. There's no trust boundary to speak of.

What pnpm 11 changed on security

The 24-hour hold (minimumReleaseAge)

The big one. By default, packages published within the last 24 hours won't install.

# pnpm-workspace.yaml
pnpm:
  minimumReleaseAge: 1440  # 분 단위 = 1일

Why does this work? Because most supply chain attacks live or die on speed. A malicious version usually survives on the registry for a few hours at most before it gets yanked. Infecting as many builds as possible inside that window is the attack. A 24-hour cooldown keeps you out of the window entirely, and buys time for Socket.dev, Snyk, and friends to flag the package first.

Put simply: when a version just went up, wait a beat before pulling it. Same instinct as not shipping a hotfix straight to prod — you run it through canary first.

You can carve out exceptions when you actually need something fast:

pnpm:
  minimumReleaseAge: 1440
  minimumReleaseAgeExclude:
    - "@types/*"
    - "my-trusted-package"

Blocking exotic sources (blockExoticSubdeps)

Packages on the npm registry can pull their own dependencies straight from a GitHub repo or a tarball URL. Those "exotic" sources are a way around standard registry auditing. Your own package.json looks spotless, and three levels down some transitive dep is fetching from a URL nobody has ever looked at.

In pnpm 11, blockExoticSubdeps: true is the default. Transitive dependencies have to come from your configured registry, and if a subdependency reaches for an external URL, the install stops. Same trust bar, applied to the whole tree.

Build script control (allowBuilds)

Lifecycle scripts like postinstall are the most common place malware actually executes. You installed a package; arbitrary code ran on your machine. pnpm 11 makes you declare which packages are allowed to run build scripts.

pnpm:
  allowBuilds:
    "electron": true
    "esbuild": true
    "core-js": false

Everything is blocked by default, and you add the packages you trust. The polarity is flipped. Instead of hoping a package isn't malicious, you decide which packages have earned the right to execute code in your environment. Same reason whitelists beat blacklists everywhere else in security.

Trust policy (trustPolicy)

pnpm:
  trustPolicy: no-downgrade

This refuses to install packages that have gotten less trustworthy than their previous version — say, a latest release that lost its signature. When a maintainer account gets popped, the attacker usually pushes a new version without going through the normal signing flow. A new release that suddenly has no signature is a signal on its own, and this catches it automatically.

Improvements beyond security

Native registry commands

pnpm publish, pnpm login, and friends used to shell out to the npm CLI under the hood. pnpm 11 handles all of it natively, so the npm dependency is gone. One fewer external piece in the toolchain, one fewer thing to go sideways in ops.

Built-in SBOM generation

pnpm sbom

You get a software bill of materials in CycloneDX 1.7 or SPDX 2.3 JSON. If you're in a regulated industry like finance, or you have to clear vendor security reviews, this is a big deal. Generating an SBOM used to mean bolting on a separate tool and adding a build step. Now it's one package manager command.

SQLite-backed store

The old store kept one JSON file per package for indexing. Store v11 replaces that with a single SQLite database. Fewer syscalls where there used to be one per file, and installs get noticeably faster — around 2.3 seconds with a warm cache and a lockfile. It's the classic move: collapse a pile of tiny file reads into one DB query.

Migrating from npm to pnpm

It's simpler than it sounds.

1. Install pnpm

npm install -g pnpm
# 또는 독립 설치
curl -fsSL https://get.pnpm.io/install.sh | sh -

2. Import your existing project

pnpm import

This converts package-lock.json into pnpm-lock.yaml. Your package.json stays untouched.

3. Install dependencies

pnpm install

4. Configure the security settings

# pnpm-workspace.yaml
pnpm:
  minimumReleaseAge: 1440
  blockExoticSubdeps: true
  trustPolicy: no-downgrade
  allowBuilds:
    "electron": true
    "esbuild": true
    # 빌드 스크립트가 필요한 패키지들 추가

One gotcha

pnpm uses stricter module resolution by default. If your code reaches for packages you never declared in package.json — phantom dependencies — you'll get errors. Your first reaction will be "why is working code suddenly broken." It's actually good news. npm's flat node_modules was hiding those dependencies by accident, and pnpm is surfacing them. You may need a few small fixes in the codebase, and you come out with honest dependency declarations.

Quick comparison

기능 npm pnpm 11
디스크 사용량 프로젝트마다 복제 하드링크 공유 저장소
설치 속도 기준선 더 빠름 (SQLite 저장소)
24시간 대기 정책 없음 기본 활성화
외부 의존성 차단 없음 기본 활성화
빌드 스크립트 제어 기본 모두 실행 명시적 허용 목록
신뢰 정책 없음 no-downgrade 옵션
SBOM 생성 없음 내장

What's coming: pnpm 12

pnpm 12 will bring Pacquet, a Rust-based install engine. Early benchmarks put warm installs at under a second, down from 2.3, and cold installs around 3.1 seconds, down from 4.7. The gap with npm is going to widen. But install speed hits diminishing returns pretty quickly, and personally I think the security policies above are the more decisive difference.

Wrapping up

For a side project or a small app with no CI/CD, there's no urgency. You're typing install yourself, so there's a human in the loop every time. Automated pipelines are the problem. If you're running production infrastructure or your deploys fire on their own, pnpm 11's security features are less an option than a baseline.

This comes down to where you draw the trust boundary. npm never really drew one. pnpm 11 makes you spell it out in a few lines of config. The migration is cheap, the ongoing maintenance is near zero, and the boundary gets a lot sharper.

I still remember pushing a batch of packages into production a few years back, checking by hand every single time whether a given version was actually safe. If these features had existed then, they'd have headed off a fair number of incidents. Can't be the only one who feels that.

Was this post helpful?

One click helps me write the next one

#pnpm#npm#Package Managers#Security#Node.js