GHSA-2rx9-3g3h-c2jv: Path Traversal
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 nodemodules 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 ERRPNPMINVALIDDEPENDENCYNAME.
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/safejoinmodulesdir.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).
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
npm/pnpmto a version that resolves this vulnerability.Fixed in 12.0.0-alpha.5
Event History
Frequently Asked Questions
Which installation workflows are exposed?
Installations using --trust-lockfile or a frozen lockfile are affected when the lockfile contains crafted dependency aliases or virtual-store slot paths. The issue can affect direct and transitive dependency links, package links, bin destinations, hoisted entries, and global virtual-store slots.
What does an attacker need to exploit this?
An attacker needs a crafted lockfile containing traversal-style dependency keys or slot components, such as ../../escaped-link. Exploitation is triggered when the affected installation workflow materializes filesystem directories or links from that lockfile.
What can be done if the patch cannot be deployed immediately?
Do not use --trust-lockfile or frozen-lockfile installation modes with lockfiles that are not trusted and reviewed. Treat lockfiles as security-sensitive input because crafted entries can cause links to be created outside the intended project or node_modules boundary.
How can a patched installation indicate that it blocked malicious input?
Rejected dependency names preserve the ERR_PNPM_INVALID_DEPENDENCY_NAME error. The validation applies before store initialization, including the current-lockfile fast path, so warm installs should also reject invalid names or slot paths.