{"id":"GHSA-2rx9-3g3h-c2jv","title":"pnpm: pacquet trust-lockfile install can create dependency symlinks outside the project","summary":"pnpm: pacquet trust-lockfile install can create dependency symlinks outside the project","severity":"high","cvss":7.1,"cwe":["CWE-22","CWE-59","CWE-73"],"vendor":"pnpm","product":"pnpm","ecosystem":"npm","affected":["pnpm >= 12.0.0-alpha.0, < 12.0.0-alpha.5"],"patched":["pnpm 12.0.0-alpha.5"],"published":"2026-09-01","updated":"2026-09-01","source":"GHSA","sourceUrl":"https://github.com/advisories/GHSA-2rx9-3g3h-c2jv","references":[{"url":"https://github.com/pnpm/pnpm/security/advisories/GHSA-2rx9-3g3h-c2jv"},{"url":"https://github.com/pnpm/pnpm/pull/12872"},{"url":"https://github.com/pnpm/pnpm/commit/51300fd41c5e4c8f47635108e373cc3d1f324fa7"},{"url":"https://github.com/advisories/GHSA-2rx9-3g3h-c2jv"}],"tags":["ghsa","npm"],"ingestedAt":"2026-09-01T19:31:23.210Z","slug":"GHSA-2rx9-3g3h-c2jv","body":"## Overview\n\n## Summary\n\nA 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.\n\n## Security boundary\n\n- A shared safe-join helper rejects traversal, absolute, platform-specific, and reserved dependency names before filesystem materialization.\n- Direct and transitive dependency links, package links, bin destinations, and public/private hoist destinations use the same containment rule.\n- Global virtual-store slots validate the complete slot path, including version-derived components, before directory creation.\n- Snapshot slots and package names are checked before store initialization and before the current-lockfile fast path, closing the warm-install bypass.\n- Rejections preserve `ERR_PNPM_INVALID_DEPENDENCY_NAME`.\n\n## Exploit replay\n\nBefore 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.\n\n## Files changed\n\n- `pacquet/crates/package-manager/src/safe_join_modules_dir.rs` defines the shared containment rule.\n- Install, symlink, bin, hoist, virtual-store, and frozen-lockfile paths call that helper before filesystem materialization.\n- The corresponding `tests.rs` files cover every sink, including warm installs and global virtual-store slots.\n\n## Commands run\n\n```text\n$ cargo test --locked -p pacquet-package-manager --lib\nPASS: 434 tests\n$ cargo clippy --locked -p pacquet-package-manager --all-targets -- --deny warnings\nPASS\n$ cargo fmt --all -- --check\nPASS\n```\n\n## Validation\n\n- Full pacquet package-manager suite: 434 passed.\n- Focused regressions cover direct and transitive aliases, bins, hoists, package names, global virtual-store version traversal, and a poisoned prior-install slot.\n- `cargo clippy -p pacquet-package-manager --all-targets -- -D warnings`: passed.\n- `cargo fmt --all -- --check` and `git diff --check`: passed.\n\n## Compatibility\n\nValid 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.\n\n---\nWritten by an agent (Codex, GPT-5).\n\n## Affected packages\n\n- `pnpm >= 12.0.0-alpha.0, < 12.0.0-alpha.5`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `pnpm 12.0.0-alpha.5`","depth":"twilight","depthScore":39,"depthScoreParts":{"impact":39.1,"likelihood":0,"exploitation":0,"ransomware":0},"changes":[]}