Skip to content
Fran Gonzalez
← Back to blog
(updated Jul 16, 2026)·Clanker·10 min read

What Changed in pnpm Since 11.1.2 Until 11.7.0

Six pnpm releases shipped while my project was frozen at 11.1.2. Two of them added security controls I should have adopted sooner.

Some matmuls wrote this slop, sorry. My goal with this content is to document some work I (a real human bean) do while poking the Clanker, and try to learn something along the way.

Six pnpm releases shipped between 11.1.2 and 11.7.0. The 11.3 and 11.4 releases added supply-chain security controls or patched vulnerabilities I should have adopted sooner. I’d also been sitting on 11.1’s audit signatures since May. I pinned my project to 11.1.2 and didn’t look up until now. The security additions were the headline; two migration behaviors that weren’t in the changelog ate most of the actual time.

Upgrade baseline

My pnpm-workspace.yaml had comprehensive supply chain controls: minimumReleaseAge, trustPolicy: no-downgrade, blockExoticSubdeps, allowBuilds, strictDepBuilds, and the rest. But three things were missing.

First, my CI ran pnpm audit --prod and pnpm audit --audit-level high, but neither verified that the packages on disk matched what the registry signed. The advisory database says whether a package version has known CVEs. It does not say whether the tarball you downloaded is the one the maintainer published. ECDSA registry signatures fill that gap, and pnpm 11.1 added a command for it.

Second, strictPeerDependencies: true catches peer dependency issues during pnpm install, but my CI uses preferFrozenLockfile which skips resolution when the lockfile is satisfied. A dependency upgrade that introduces a peer conflict passes pnpm install silently if the lockfile was generated by a different machine. I had no way to scan the existing lockfile for peer issues.

Third, minimumReleaseAge and trustPolicy re-verify every package on every pnpm install, even when the lockfile hasn’t changed and was already verified on the machine that generated it. In CI with preferFrozenLockfile, this re-verification is redundant. It is also the most memory-intensive part of the install.

Beyond feature gaps, 11.4 patched five supply-chain vulnerability classes I hadn’t tracked: lockfile integrity validation, credential scoping across registries, git resolution hardening, patch file integrity, and dependency alias attacks.

Release-by-release changes

pnpm 11.0: Settings moved out of package.json

This was the change that bit me first. Since v11, pnpm no longer reads the pnpm field in package.json. The settings that used to live there (overrides, peerDependencyRules, packageExtensions, and the rest) all moved to pnpm-workspace.yaml. This is part of the v10 to v11 migration, and there is an official codemod for it: pnpx codemod run pnpm-v10-to-v11.

The deprecation prints a warning and keeps going:

[WARN] The "pnpm" field in package.json is no longer read by pnpm.
The following keys were ignored: "pnpm.overrides".

In a long install log that warning is easy to miss, and the install still completes. The override silently has no effect, and the lockfile does not change. I cover what that cost me in Two behaviors that bit me during the migration.

pnpm 11.1: pnpm audit signatures

The pnpm audit signatures command verifies that every installed package has a valid ECDSA signature from the registry’s public key. Each registry publishes its signing keys at /-/npm/v1/keys. pnpm fetches them, checks the signature on each package’s integrity record, and exits with code 1 if anything fails.

This is different from pnpm audit (which checks against the npm advisory database for known CVEs) and trustPolicy (which verifies provenance attestations haven’t been downgraded). Signatures verify the artifact; audits verify the version; trust policy verifies the supply chain hasn’t regressed.

I added it to the Dagger CI pipeline right after the advisory audits:

// ci/src/index.ts — standalone @func()
@func()
async auditSignatures(source: Directory): Promise<string> {
  return this.nodeBase(source)
    .withExec(["pnpm", "audit", "signatures"])
    .stdout();
}

// In the chained check() pipeline, it runs third:
const steps = [
  { name: "audit-prod", cmd: ["pnpm", "audit", ...] },
  { name: "audit-all", cmd: ["pnpm", "audit", ...] },
  { name: "audit-signatures", cmd: ["pnpm", "audit", "signatures"] },
  // ...
];

The command respects scoped registries configured in pnpm-workspace.yaml. Registries that don’t publish signing keys are skipped. With --json, the output is machine-readable.

pnpm 11.1 also added:

  • pnpm bugs and pnpm owner: open a package’s bug tracker or list its owners from the CLI
  • Install from arbitrary named registries, including a built-in alias for GitHub Packages npm registry
  • --no-runtime to skip devEngines.runtime installation in CI

pnpm 11.3: trustLockfile and native commands

The trustLockfile setting skips the supply-chain verification pass that re-applies minimumReleaseAge and trustPolicy to lockfile entries on every install. When enabled, pnpm treats the lockfile as already verified.

I enabled it in pnpm-workspace.yaml:

# pnpm-workspace.yaml
trustLockfile: true

The tradeoff: a poisoned lockfile committed by a compromised collaborator would bypass verification. In my case, this project has no outside collaborators and every commit passes CI with pnpm audit signatures and advisory audits. The lockfile is reviewed in PRs. The risk is minimal and the CI speedup is real. The docs call this out explicitly: it is for “closed-source projects with trusted authors.”

11.3 also shipped native TypeScript implementations of pnpm pkg, pnpm repo, and pnpm set-script, replacing the npm CLI fallback. These are minor quality improvements; they don’t change behavior but remove a dependency on npm being installed.

Two other 11.3 additions I noted but didn’t adopt:

  • pnpm stage: staged publishing for npm packages (I don’t publish packages from this project)
  • --skip-manifest-obfuscation for pnpm pack and pnpm publish (same reason)

pnpm 11.4: Supply-chain patches

This was the most impactful release for security. It closed five vulnerability classes:

Tarball-integrity mismatches are now a hard failure. If a package tarball’s integrity hash doesn’t match the lockfile, pnpm install fails instead of warning. You can opt in to refreshing integrity values from the registry with --update-checksums, but --force and pnpm update don’t bypass the check. This prevents a corrupted or tampered tarball from being silently accepted.

Unscoped credentials no longer leak across registries. A credential configured for one registry (//registry.npmjs.org/:_authToken) was previously usable by other registries in some configurations. This is now blocked.

Lockfile entries without integrity are rejected. A missing integrity field on a lockfile entry is treated as a validation error, not silently accepted.

Git resolutions reject non-SHA commit fields. Tags and branch names in resolution.commit are rejected. Only full SHA-1 hashes pass validation.

Patch file and dependency alias hardening. The exact details are in the release notes, but the summary is that crafted patch files and dependency aliases can no longer redirect package resolution.

Each of these is a defense against a known attack class. None of them required configuration changes on my side; the protections apply by default.

What I already had

Some features shipped in pnpm 11.0 and 11.1 that were already in my config from the start:

  • pnpm peers check (11.0) analyzes the lockfile for unmet and missing peer dependencies. I added it to CI as a standalone step:

    // ci/src/index.ts
    { name: "peers-check", cmd: ["pnpm", "peers", "check"] },

    This catches peer dependency regressions between preferFrozenLockfile installs.

  • devEngines.packageManager (11.0) is the newer way to declare pnpm version ranges. I started this migration on the packageManager field, then removed it once mise.toml became the single source of truth (see Verification).

  • pnpm create now honors project security policies (minimumReleaseAge, trustPolicy) when fetching starter kits.

What I skipped

Not every new feature applies to a static blog:

  • devEngines.runtime: automatically downloads Node.js. I use mise for that; adding a second runtime manager would create conflicts.
  • pnpm stage: staged publishing. No packages to publish.
  • pnpm version: bump package versions. This project has a single root package with no versioning needs.
  • Install from arbitrary registries: I use the public npm registry exclusively.

Two behaviors that bit me during the migration

The version bump itself was mechanical: the same 11.1.2 to 11.7.0 replacement across CI workflows, Dagger modules, mise.toml, and docs. Two behaviors ate the rest of the time, and neither is called out in the changelog.

The silent overrides drop

I put an esbuild override in package.json first, ran install, and the audit still flagged esbuild. The override had been silently dropped, because of the v11 field move above. Moving it to pnpm-workspace.yaml fixed it on the next install:

- # package.json
- "pnpm": {
-   "overrides": { "esbuild": "0.28.1" }
- }

+ # pnpm-workspace.yaml
+ overrides:
+   "esbuild@0.28.0": "0.28.1"

The pnpm settings docs are the new home for overrides. git log -p package.json before the bump would have shown the pnpm block was already dead weight, but I only noticed when the audit refused to clear.

peerDependencyRules.allowedVersions is a maintenance pin

pnpm peers check failed on this project:

✕ unmet peer tailwindcss
  Installed: 4.3.1
  Wanted:
    ">=3.0.0 || >=4.0.0 || insiders":
      @tailwindcss/typography@0.5.20

Tailwind 4.3.1 is inside >=4.0.0, so the peer is satisfied. pnpm’s strict peer check could not evaluate the range, because insiders is a Tailwind release tag, and pnpm expects a semver comparator there. @tailwindcss/typography@0.5.20 ships a peer range pnpm cannot parse.

The previous pnpm-workspace.yaml already had a workaround:

peerDependencyRules:
  allowedVersions:
    "@tailwindcss/typography>tailwindcss": "4.3.0"

I had read the exact version 4.3.0 as drift, since the installed version was 4.3.1, and removed it. That forced pnpm to try parsing the upstream range again, and the check failed. The rule is a maintenance pin for an upstream peer range pnpm cannot parse, and peerDependencyRules.allowedVersions is the documented escape hatch for exactly that.

The fix is to use a range instead of an exact version, so the next Renovate bump to 4.3.2 does not re-trip the same failure:

  peerDependencyRules:
    allowedVersions:
-     "@tailwindcss/typography>tailwindcss": "4.3.0"
+     "@tailwindcss/typography>tailwindcss": "^4.3.0"

Verification

The version bump and the security additions landed in a handful of lines:

# mise.toml
- pnpm = "11.1.2"
+ pnpm = "11.7.0"

# pnpm-workspace.yaml
+ trustLockfile: true

# ci/src/index.ts (Dagger)
- .withExec(["npm", "install", "-g", "pnpm@11.1.2"])
+ .withExec(["npm", "install", "-g", "pnpm@11.7.0"])
+ { name: "audit-signatures", cmd: ["pnpm", "audit", "signatures"] },

package.json keeps engines.pnpm at >=11.7.0 <12. The packageManager field was still present at this point; I removed it later, once mise.toml became the single source of truth and Corepack kept drifting from it.

pnpm audit signatures passed clean. All 111 packages in the dependency tree carry valid ECDSA signatures from the npm registry. That is the expected result for a project that only uses npm-registry packages, but it is still worth verifying.

pnpm peers check passed clean as well, with the @tailwindcss/typography peer pin from above in place. Without that pin, the check fails on the unparseable insiders tag.

trustLockfile skips the re-verification pass on subsequent CI runs. I did not benchmark the difference on this project (the tree is small), but the release notes note a real memory reduction for large workspaces.

Next upgrade

I’d check the pnpm release page every month instead of waiting three months and six releases. The 11.4 supply-chain patches in particular were worth adopting immediately. A pnpm self-update run and a quick scan of the release notes takes two minutes and would have caught the 11.4 fixes when they shipped. I’d also read git log -p on pnpm-workspace.yaml before pruning a rule that looks like drift; the peerDependencyRules pin looked stale until the peers check failed without it.

References

This post was written with AI assistance.