Listen to this Post
The virtual store linker constructs package installation directories using path.join(modules, pkgName), where `pkgName` is extracted from lockfile `packages` keys via `dp.parse(depPath).name` without validation. A crafted `pnpm-lock.yaml` with traversal sequences in `depPath` keys (e.g., ../../../tmp/[email protected]) causes package content to be written to arbitrary filesystem paths during pnpm install. This is an incomplete fix of GHSA-fr4h-3cph-29xv — the `safeJoinModulesDir` containment helper was applied to the hoisted linker and `symlinkDependency` but NOT to the virtual store linker’s lockfileToDepGraph.ts:233.
Root Cause: `dp.parse()` at `pnpm11/deps/path/src/index.ts:135` extracts the package name via a raw substring operation with zero validation that `name` is a valid npm package name. A `depPath` of `../../../tmp/[email protected]` yields name = '../../../tmp/pwned'.
Vulnerable Code Path:
– `pnpm-lock.yaml` → `lockfile.packages[‘../../../../../../../tmp/[email protected]’]` (attacker-controlled lockfile key)
– `nameVerFromPkgSnapshot(depPath, pkgSnapshot)` at `lockfile/utils/src/nameVerFromPkgSnapshot.ts:16` → calls `dp.parse(depPath)` → returns `{ name: ‘../../../../../../../tmp/pwned’ }`
– `lockfileToDepGraph.ts:232` → `modules = path.join(dirInVirtualStore, ‘node_modules’)`
– `lockfileToDepGraph.ts:233` → `dir = path.join(modules, pkgName)` → resolves to `/tmp/pwned` (ESCAPES virtual store)
– `storeController.importPackage(depNode.dir, …)` → writes package content to the traversed path
Why Existing Defenses Don’t Catch It:
– `depPathToFilename()` — replaces `/` with `+` for the `dirInVirtualStore` path, but `pkgName` comes SEPARATELY from `dp.parse()` and is NOT passed through this function
– `verifyLockfileResolutions()` — validates dependency map keys (aliases) via isValidDependencyAlias(), but never validates the `depPath` keys themselves
– Lockfile parser — `yaml.load(lockfileRawContent)` with no schema validation on `packages` keys
– `importPackage()` — accepts `targetDir` and passes it directly to `cafsStore.importPackage(targetDir, …)` with zero containment check
– Integrity verification — requires a real fetchable package but does not validate the destination path
Escalation to RCE (non-default config): When `dangerouslyAllowAllBuilds: true` is configured (or the traversal package name is in the explicit `allowBuilds` list), the same traversed path is used in the rebuild phase at after-install/src/index.ts:402,470. The attacker’s `postinstall` script then executes with the victim’s shell access. Under default config, `allowBuild` returns false for unknown packages, limiting impact to arbitrary file write.
Also Affected (PnP linker): When `nodeLinker: pnp` is configured, `lockfileToPackageRegistry()` at `lockfile/to-pnp/src/index.ts:105-110` uses the same unvalidated `dp.parse().name` in `packageLocation` construction, allowing the `.pnp.cjs` resolver map to point outside the virtual store.
DailyCVE Form:
Platform: pnpm
Version: <10.34.5, 11.0.0-11.10.x
Vulnerability: Path Traversal
Severity: High (CVSS 7.1)
date: 2026-08-31
Prediction: 2026-09-14
What Undercode Say:
Analytics:
Check pnpm version pnpm --version Check if vulnerable if [[ $(pnpm --version) =~ ^10.[0-9]+.[0-9]+$ ]] && [[ $(pnpm --version) < "10.34.5" ]]; then echo "VULNERABLE: pnpm < 10.34.5" elif [[ $(pnpm --version) =~ ^11.[0-9]+.[0-9]+$ ]] && [[ $(pnpm --version) < "11.11.0" ]]; then echo "VULNERABLE: pnpm 11.0.0-11.10.x" else echo "SAFE: pnpm $(pnpm --version)" fi
Exploit: (Educational Purposes!)
Craft a malicious `pnpm-lock.yaml`:
lockfileVersion: '9.0' packages: ../../../../../../../tmp/[email protected]: resolution: {integrity: sha512-<real-package-integrity>} engines: {node: '>=14'} snapshots: ../../../../../../../tmp/[email protected]: {} importers: .: dependencies: legitimate-name: specifier: ^1.0.0 version: ../../../../../../../tmp/[email protected]
Run:
pnpm install Package content is written to /tmp/pwned/ instead of the virtual store
Protection:
- Upgrade to pnpm 10.34.5 or 11.11.0 or later
- Do not run `pnpm install` on untrusted lockfiles
- Avoid enabling `dangerouslyAllowAllBuilds: true`
– Use `–ignore-scripts` flag when installing from untrusted sources
Impact:
- Arbitrary file writes on the machine of any user who runs `pnpm install`
– Targets include: `.git/hooks/pre-commit` (code execution on next git operation), `~/.local/bin/` (binary hijacking), and project source files (supply chain injection) - Remote Code Execution (RCE) when `dangerouslyAllowAllBuilds: true` or matching `allowBuilds` entry permits lifecycle scripts
🎯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

