Skip to main content
CVE-2026-82393VulnerabilityPublished Updated

pnpm@10 · pnpm: A tarball dependency's manifest `name` escapes node_modules → arbitrary file write/overwrite on install

Affects pnpm@10Source GCVE Database

0

Not known to be exploited · CVSS 7.5 High : upgrade to 10.34.5 soon.

Data as of 26 Oct 2026, 17:16 UTC

CVSS 3.1 base score
7.5High
How severe the flaw is on the 0–10 CVSS scale.
EPSS
0.6% chance of exploitation in 30 days – higher than 49% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
Patch
Available – fixed in 10.34.5
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

Exploitation evidence

  1. Disclosed: evidence found.

    CVE published 3 Sep 2026

  2. Detectable: no evidence on record.

    No public nuclei template indexed

  3. Weaponised: no evidence on record.

    No public exploit code indexed in Metasploit or Exploit-DB

  4. Exploited in the wild: no evidence on record.

    Not in CISA KEV and no source reports exploitation

  5. Observed by OffSeq Mirage: no evidence on record.

    No attack traffic recorded by OffSeq Mirage sensors

A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.

What to do

Upgrade pnpm@10 to 10.34.5.

Affected software

ProductAffectedFixed in
Homebrewpnpm@10pkg:brew/pnpm%4010Other threatsAffected
  • >=10.33.2 <10.34.5
Fixed in
  • 10.34.5

Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.

Description

## Summary When resolving a package, pnpm uses the resolved **manifest `name`** as a raw path segment for the isolated-linker import target. A tarball dependency whose `package.json` `name` is a scoped path traversal (`@x/../../…/<abs path>`) is therefore extracted **outside `node_modules`**, to an attacker-chosen absolute path, and can **overwrite existing files** there. Attacker controls the destination, filenames, and contents → arbitrary file write → **code execution** (e.g. `~/.zshrc`, `.git/hooks/pre-commit`, another package's code). Occurs during `pnpm install` **even with `--ignore-scripts`** (no lifecycle scripts run), defeating that safety. Same class as the just-patched **GHSA-hwx4** (transitive-dependency *alias* traversal) and **GHSA-v23m** (`stage download` manifest name/version traversal), in a sink their fixes did not cover: the isolated-linker import target keyed by the resolved **name**. ## Root cause - The isolated-linker import target is built with a raw `path.join(modules, <resolved name>)` in `installing/deps-resolver/src/resolvePeers.ts:706`, `installing/deps-resolver/src/index.ts:614`, and `deps/graph-builder/src/lockfileToDepGraph.ts:233` — **without** the `safeJoinModulesDir` guard used on the symlink/hoisted/bin paths (`installing/deps-restorer/src/lockfileToHoistedDepGraph.ts:222`). The store location is `node_modules/.pnpm/<id>/node_modules/<name>`, so a traversal `<name>` escapes. - The only resolve-time name gate (`resolving/npm-resolver/src/pickPackage.ts:753`) rejects only *unscoped* names containing `/`, so a **scoped** `@x/../..` passes. ## Steps to reproduce Self-contained PoC (real `pnpm@11.9.0`; loopback tarball server; escape target is a throwaway temp dir): ``` npm i pnpm@11.9.0 # host a tarball whose package.json name = "@x/"+"../".repeat(25)+"<abs>/OUTSIDE"; victim depends on the http URL pnpm install --ignore-scripts ``` Confirmed output (`repro/poc.mjs`, exit 0): ``` escape dir is outside the project : true new file implanted outside node_modules : true pre-existing file OVERWRITTEN : true *** CONFIRMED: a tarball dependency wrote & overwrote files OUTSIDE the project during `pnpm install --ignore-scripts` *** ``` ## Remediation Route the isolated-linker import-target joins (`resolvePeers.ts:706`, `deps-resolver/index.ts:614`, `lockfileToDepGraph.ts:233`) through `safeJoinModulesDir` (as the hoisted linker already does), and/or enforce `validate-npm-package-name` on the resolved manifest name (close the scoped-name gap at `pickPackage.ts:753`) so the import target rejects a traversal name and re-asserts containment before any write.

References

Community reviews

Loading reviews…