GHSA-vq4v-j7r6-jq4m: Path Traversal

Published Sep 2, 2026
·
Updated

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 nodemodules, 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 nodemodules/.pnpm/<id>/nodemodules/<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 nodemodules : 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.

Affected Software

2 affected componentsFixes available
npm/pnpm>=11.0.0<11.11.0
11.11.0
npm/pnpm<10.34.5
10.34.5

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/pnpm to a version that resolves this vulnerability.

    Fixed in 11.11.0
  2. Upgrade

    Upgrade npm/pnpm to a version that resolves this vulnerability.

    Fixed in 10.34.5
  3. Configuration

    Route the isolated-linker import-target joins in resolvePeers.ts:706, installing/deps-resolver/src/index.ts:614, and deps/graph-builder/src/lockfileToDepGraph.ts:233 through safeJoinModulesDir (matching the guard used for symlink/hoisted/bin paths in installing/deps-restorer/src/lockfileToHoistedDepGraph.ts:222) so traversal in resolved names cannot escape node_modules.

    pnpm isolated-linker dependency import target (installing/deps-resolver/src/resolvePeers.ts:706; installing/deps-resolver/src/index.ts:614; deps/graph-builder/src/lockfileToDepGraph.ts:233) safeJoinModulesDir guard = use safeJoinModulesDir for join(modules, <resolved name>) instead of raw path.join(modules, <resolved name>)
  4. Configuration

    Close the scoped-name gap by enforcing validate-npm-package-name on the resolved manifest name (the only current gate in pickPackage.ts:753 rejects only unscoped names containing '/', so scoped traversal names pass). Reject traversal-containing resolved manifest names before using them as import-target path segments.

    pnpm package name validation at resolve time (resolving/npm-resolver/src/pickPackage.ts:753) validate-npm-package-name gate = enforce validate-npm-package-name (or equivalent) on the resolved manifest name to reject scoped traversal names like @x/../../…/<abs path>
  5. Compensating control

    As a containment measure, prevent pnpm from resolving/extracting attacker-controlled tarballs whose package.json name contains traversal (e.g., scoped path traversal like @x/../../…/<abs path>), for example by restricting allowed dependency sources or validating tarball package.json names in your supply chain before running pnpm install.

Event History

Sep 2, 2026
Advisory Published
via GitHub·02:36 PM
Data Sourced
via GitHub·02:36 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Does using --ignore-scripts prevent exploitation?

No. The traversal occurs during pnpm install while resolving and extracting the dependency, even when lifecycle scripts are disabled with --ignore-scripts.

2

What does an attacker need to provide to trigger the issue?

A tarball dependency whose package.json manifest name is a crafted scoped path-traversal value, such as one containing @x/../../ followed by an absolute path. The attacker-controlled manifest name is used as part of the isolated-linker import destination.

3

What could an attacker overwrite if exploitation succeeds?

The attacker controls the destination path, filenames, and file contents, enabling writes to an attacker-chosen absolute path outside node_modules. Examples given include shell startup files, Git hooks, or another package's code, which can lead to code execution.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203