CVE-2026-55698: pnpm Env Lockfile Package-Manager Integrity Bypass
A malicious repository can commit a crafted pnpm-lock.yaml that tricks pnpm into downloading and executing an attacker-chosen pnpm binary instead of fetching a fresh, verified one from the registry.
The problem
When pnpm's automatic version-switching kicks in (the `packageManager` field differs from the running version), `switchCliVersion()` reads `packageManagerDependencies` from the committed env lockfile.
Before the patch, if the committed lockfile already contained matching `pnpm` and `@pnpm/exe` version entries, `resolvePackageManagerIntegrities()` treated them as fully resolved and skipped the registry re-fetch. That committed metadata, including any attacker-supplied `integrity` hash, was passed directly to `installPnpmToStore()` and the resulting binary was executed with `spawn.sync()` under the victim's privileges.
Proof of concept
# Commit this as the first YAML document in pnpm-lock.yaml
# (the env-lockfile section pnpm reads for package-manager bootstrap)
importers:
.:
configDependencies: {}
packageManagerDependencies:
"@pnpm/exe":
specifier: "9.3.0"
version: "9.3.0"
pnpm:
specifier: "9.3.0"
version: "9.3.0"
lockfileVersion: "9.0"
packages:
/pnpm@9.3.0:
resolution:
integrity: sha512-poisoned # replace with integrity of attacker-controlled tarball
snapshots:
/pnpm@9.3.0: {}
# Trigger: victim runs any pnpm command in this repo while a different
# pnpm version is active (onFail=download path). pnpm enters
# switchCliVersion(), finds the version match, skips re-resolution,
# installs from the poisoned integrity field, and spawns the result.The root cause is in `installing/env-installer/src/resolvePackageManagerIntegrities.ts`. Its version-only fast path returned immediately when both `pnpm` and `@pnpm/exe` version strings already matched the wanted version, without re-fetching from a trusted registry.
This is CWE-345 (insufficient authenticity verification) compounded by CWE-494 (download without integrity check) and CWE-829 (inclusion of functionality from untrusted control sphere).
The patch adds a `force` parameter to `resolvePackageManagerIntegrities()`. `switchCliVersion()` now always passes `force: true` for already-present package-manager entries, bypassing the version-only fast path, and reassigns the resolver's return value back to `envLockfile`.
The installer receives freshly resolved, registry-verified metadata instead of whatever the committed lockfile contains.
The fix
Upgrade pnpm to 10.34.2 (or 11.x equivalent containing patch commit a93449314f398cf4bdf2e28d033c02d37395ad22). The patched `switchCliVersion()` force-re-resolves package-manager bootstrap metadata through trusted registries before any install or execution, regardless of whether a matching version entry already exists in the committed env lockfile.