Summary
pnpm: Virtual store linker path traversal via unvalidated depPath name in lockfileToDepGraph
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.
Details
Root Cause
dp.parse() at pnpm11/deps/path/src/index.ts:135 extracts the package name as:
const name = dependencyPath.substring(0, sepIndex)
This is 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)atlockfile/utils/src/nameVerFromPkgSnapshot.ts:16→ callsdp.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 thedirInVirtualStorepath, butpkgNamecomes SEPARATELY fromdp.parse()and is NOT passed through this functionverifyLockfileResolutions(), validates dependency map keys (aliases) viaisValidDependencyAlias(), but never validates the depPath keys themselves- Lockfile parser,
yaml.load(lockfileRawContent)with no schema validation onpackageskeys importPackage(), acceptstargetDirand passes it directly tocafsStore.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. This is a lower-impact variant (PnP is not the default linker).
Reproduction
Craft a 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.
Relationship to GHSA-fr4h-3cph-29xv
GHSA-fr4h-3cph-29xv fixed the hoisted linker path (lockfileToHoistedDepGraph.ts:222) by adding safeJoinModulesDir. The same fix was NOT applied to the virtual store linker, which uses the identical dp.parse().name → path.join() pattern at lockfileToDepGraph.ts:233.
Impact
An attacker who can commit a crafted pnpm-lock.yaml to a repository (or supply one via a malicious package) can cause arbitrary file writes on the machine of any user who runs pnpm install. Written content is the actual package files from a real npm package (attacker controls which package and which destination).
Targets for arbitrary file write include:
.git/hooks/pre-commit, code execution on next git operation~/.local/bin/, binary hijacking- Project source files, supply chain injection
Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.
CVE-2026-82392 has a CVSS score of 7.1 (High). The vector is network-reachable, no privileges required, and user interaction required. A CVSS score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether this affects your application depends on whether the vulnerable code is present and reachable in your environment. A fixed version is available (10.34.5, 11.11.0); upgrading removes the vulnerable code path.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
Apply safeJoinModulesDir (or equivalent validation) at:
lockfileToDepGraph.ts:233,path.join(modules, pkgName)after-install/src/index.ts:402,path.join(pkgModulesDir(depPath), pkgInfo.name)lockfile/to-pnp/src/index.ts:105-110, PnPpackageLocation
Alternatively, validate depPath keys during lockfile parsing to reject any that don't produce valid npm package names via dp.parse().
Frequently Asked Questions
- What is CVE-2026-82392? CVE-2026-82392 is a high-severity path traversal vulnerability in pnpm (npm), affecting versions < 10.34.5. It is fixed in 10.34.5, 11.11.0. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
- How severe is CVE-2026-82392? CVE-2026-82392 has a CVSS score of 7.1 (High). This score reflects the worst-case severity of the vulnerability, not your specific exposure. Whether it represents real risk in your environment depends on whether the vulnerable code is present and reachable.
- Which versions of pnpm are affected by CVE-2026-82392? pnpm (npm) versions < 10.34.5 is affected.
- Is there a fix for CVE-2026-82392? Yes. CVE-2026-82392 is fixed in 10.34.5, 11.11.0. Upgrade to this version or later.
- Is CVE-2026-82392 exploitable, and should I be worried? Whether CVE-2026-82392 is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether CVE-2026-82392 is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix CVE-2026-82392?
- Upgrade
pnpmto 10.34.5 or later - Upgrade
pnpmto 11.11.0 or later
- Upgrade