---
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'
---

## Overview

## 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).

## Affected packages

- `pnpm >= 12.0.0-alpha.0, < 12.0.0-alpha.5`

## Remediation

Upgrade to a patched release:

- `pnpm 12.0.0-alpha.5`
