GHSA-vq4v-j7r6-jq4m

Suggest an improvement
Source
https://github.com/advisories/GHSA-vq4v-j7r6-jq4m
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-vq4v-j7r6-jq4m/GHSA-vq4v-j7r6-jq4m.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-vq4v-j7r6-jq4m
Aliases
Downstream
CGA (10)
MINI (1)
Published
2026-09-02T14:36:59Z
Modified
2026-09-02T14:45:05Z
Severity
  • 7.5 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H CVSS Calculator
Summary
pnpm: A tarball dependency's manifest `name` escapes node_modules → arbitrary file write/overwrite on install
Details

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.

Database specific
{
    "cwe_ids":  [
        "CWE-22",
        "CWE-73",
        "CWE-94"
    ],
    "github_reviewed":  true,
    "github_reviewed_at":  "2026-09-02T14:36:59Z",
    "nvd_published_at":  "2026-08-31T22:17:22Z",
    "severity":  "HIGH"
}
References

Affected packages

npm / pnpm

Package

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
10.34.5

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-vq4v-j7r6-jq4m/GHSA-vq4v-j7r6-jq4m.json"

npm / pnpm

Package

Affected ranges

Type
SEMVER
Events
Introduced
11.0.0
Fixed
11.11.0

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/09/GHSA-vq4v-j7r6-jq4m/GHSA-vq4v-j7r6-jq4m.json"