Listen to this Post
The vulnerability resides in pnpm’s automatic package-manager version switching mechanism. When a user runs `pnpm` directly in a repository that specifies a `packageManager` field (e.g., "packageManager": "[email protected]"), pnpm checks if the currently running version matches the wanted version. If not, it enters `switchCliVersion()` and attempts to download and execute the correct version.
To optimize this process, pnpm persists package-manager bootstrap metadata in the first YAML document of pnpm-lock.yaml. This “env lockfile” contains `packageManagerDependencies` entries for `pnpm` and @pnpm/exe, along with corresponding `packages` and `snapshots` sections that specify integrity hashes and tarball locations.
The vulnerable code path begins when `lockfile/fs/src/envLockfile.ts` reads the repository’s lockfile and performs only basic shape validation. Next, `pnpm/src/main.ts` invokes `switchCliVersion()` when it detects a version mismatch and the `onFail` policy is set to download. Inside switchCliVersion(), the committed env lockfile is loaded and passed to resolvePackageManagerIntegrities().
The critical flaw exists in installing/env-installer/src/resolvePackageManagerIntegrities.ts: this function treats `packageManagerDependencies` as already resolved if the `pnpm` and `@pnpm/exe` versions match the requested ones. It does not verify the integrity or authenticity of the package metadata—it merely checks version strings. Consequently, a malicious repository can commit a lockfile containing arbitrary `packages` entries with attacker-chosen integrity hashes and tarball URLs.
When `switchCliVersion()` proceeds, it hands the env lockfile to `installPnpmToStore()` (via engine/pm/commands/src/self-updater/installPnpm.ts), which converts the `snapshots` and `packages` sections into a lockfile used by headlessInstall(). This installs the package manager from the attacker-controlled metadata without any fresh resolution from trusted registries. Finally, `switchCliVersion()` executes the installed binary via spawn.sync(), granting the attacker arbitrary code execution with the victim’s privileges.
The patch forces `switchCliVersion()` to call `resolvePackageManagerIntegrities()` with force: true, which bypasses the version-only fast path and re-resolves the package-manager entries through trusted registries before installation or execution. The helper function itself retains the fast path for non-execution callers, but the execution boundary is now secured.
DailyCVE Form:
Platform: pnpm (npm)
Version: main (before patch)
Vulnerability: Lockfile trust bypass
Severity: High (CVSS 8.8)
date: 2026-06-09
Prediction: 2026-06-16
What Undercode Say:
Analytics and validation commands from the patch branch:
TypeScript builds ./node_modules/.bin/tsgo --build installing/env-installer/tsconfig.json ./node_modules/.bin/tsgo --build pnpm/tsconfig.json Focused Jest regression tests PNPM_REGISTRY_MOCK_PORT=7799 NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" ../node_modules/.bin/jest src/switchCliVersion.test.ts -t "re-resolved package-manager lockfile" --runInBand PNPM_REGISTRY_MOCK_PORT=7799 NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" ../node_modules/.bin/jest src/switchCliVersion.test.ts src/syncEnvLockfile.test.ts --runInBand ESLint checks ./node_modules/.bin/eslint installing/env-installer/src/resolvePackageManagerIntegrities.ts pnpm/src/switchCliVersion.ts pnpm/src/switchCliVersion.test.ts Whitespace check git diff --check
PoC snippet for constructing a poisoned env lockfile:
{
"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": {
"/[email protected]": {
"resolution": {
"integrity": "sha512-poisoned"
}
}
},
"snapshots": {
"/[email protected]": {}
}
}
Exploit:
- Attacker creates a malicious repository with a `package.json` specifying
"packageManager": "[email protected]". - Attacker commits a `pnpm-lock.yaml` containing a crafted env lockfile with matching version strings but poisoned `packages` and `snapshots` entries (e.g., a tarball from an attacker-controlled registry or a manipulated integrity hash).
- Victim clones the repository and runs `pnpm` directly (any command, e.g.,
pnpm install). - pnpm detects version mismatch, enters
switchCliVersion(), reads the committed env lockfile, and skips fresh resolution because versions match. - pnpm installs the package manager using the poisoned metadata and executes the resulting binary via
spawn.sync(). - Attacker gains arbitrary code execution in the victim’s environment with the victim’s user privileges.
Protection:
- Patch: Apply the fix that introduces `force: true` in
switchCliVersion(), forcing re-resolution through trusted registries before installation. - Workaround: Disable automatic package-manager switching by setting `onFail` to `error` or `ignore` in the repository configuration (if feasible).
- Mitigation: Use `–package-manager-strict` or similar flags to enforce fresh resolution; avoid running `pnpm` in untrusted repositories without review.
- Verification: After patching, ensure that `resolvePackageManagerIntegrities()` is called with `force: true` when the env lockfile already contains matching versions, and that the installer receives the refreshed lockfile rather than the committed one.
Impact:
- Confidentiality: Attacker can read local secrets, environment variables, and source code.
- Integrity: Attacker can alter project files, inject malicious dependencies, or modify build outputs.
- Availability: Attacker can disrupt the development or CI pipeline, delete files, or install backdoors.
- Scope: All developers and CI systems using pnpm with auto-switching enabled are vulnerable when cloning malicious repositories. The attack requires no user privileges beyond running `pnpm` and no network manipulation—the malicious lockfile is delivered through standard supply-chain channels (e.g., GitHub, npm). CVSS score remains 8.8 (High) for unpatched versions.
🎯Let’s Practice Exploiting & Learn Patching For Free:
🎓 Live Courses & Certifications:
Join Undercode Academy for Verified Certifications
🚀 Request a Custom Project:
Secure, high-velocity infrastructure and disruptive technological engineering. Contact our engineering team for high-tier development and proprietary systems:
[email protected]
💎 Smart Architecture | 🛡️ Secure by Design | ⭐ Trusted by Thousands
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

