GitHub Advisoryadvisory · npm
GHSA-2rx9-3g3h-c2jv pnpm: pacquet trust-lockfile install can create dependency symlinks outside the project
HIGH
Official page ↗
- Ecosystem
- npm
- Packages
- pnpm
- Severity
- High
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:L
- Weaknesses
- CWE-22, CWE-59, CWE-73
- Reviewed
- 2026-09-01
Affected packages
• npm pnpm: >= 12.0.0-alpha.0, < 12.0.0-alpha.5 → fixed in 12.0.0-alpha.5
Description
Summary
A crafted lockfile alias could reach several install-time filesystem joins. With `--trust-lockfile` or a frozen lockfile, traversal segments could create links outside the intended project or `node_modules` boundary. This patch validates dependency names and every virtual-store slot before creating directories, links, bins, or hoisted entries.
Security boundary
• A shared safe-join helper rejects traversal, absolute, platform-specific, and reserved dependency names before filesystem materialization.
• Direct and transitive dependency links, package links, bin destinations, and public/private hoist destinations use the same containment rule.
• Global virtual-store slots validate the complete slot path, including version-derived components, before directory creation.
• Snapshot slots and package names are checked before store initialization and before the current-lockfile fast path, closing the warm-install bypass.
• Rejections preserve `ERR_PNPM_INVALID_DEPENDENCY_NAME`.
Exploit replay
Before the patch, `pacquet install --frozen-lockfile --trust-lockfile` accepted a `../../escaped-link` dependency key and created a symlink outside the project. With this patch, the same lockfile is rejected before materialization and no outside link is created.
Files changed
• `pacquet/crates/package-manager/src/safe_join_modules_dir.rs` defines the shared containment rule.
• Install, symlink, bin, hoist, virtual-store, and frozen-lockfile paths call that helper before filesystem materialization.
• The corresponding `tests.rs` files cover every sink, including warm installs and global virtual-store slots.
Commands run
```text
$ cargo test --locked -p pacquet-package-manager --lib
PASS: 434 tests
$ cargo clippy --locked -p pacquet-package-manager --all-targets -- --deny warnings
PASS
$ cargo fmt --all -- --check
PASS
```
Validation
• Full pacquet package-manager suite: 434 passed.
• Focused regressions cover direct and transitive aliases, bins, hoists, package names, global virtual-store version traversal, and a poisoned prior-install slot.
• `cargo clippy -p pacquet-package-manager --all-targets -- -D warnings`: passed.
• `cargo fmt --all -- --check` and `git diff --check`: passed.
Compatibility
Valid unscoped and scoped dependency aliases continue to work. The reproduced escape was specific to pacquet, so this branch does not change the TypeScript CLI or the lockfile format.
---
Written by an agent (Codex, GPT-5).