CVE-2026-55699: pnpm Reserved Bin Name Deletes PNPM_HOME Vulnerability

MODERATE
Advisory Identifier: GHSA-4gxm-v5v7-fqc4
CVSS 6.5
CWE-73 CWE-22
Published
Advisory Summary

CVE-2026-55699 is a MODERATE (CVSS 6.5) reserved bin name deletes pnpm_home vulnerability (CWE-73: External Control of File Name or Path) affecting pnpm libraries in the npm ecosystem. The issue involves reserved bin name deletes pnpm_home during global remove. Remediation is available by upgrading affected packages to version 10.34.2, 11.5.3.

CVE-2026-55699 at a Glance

CVE Identifier CVE-2026-55699
GHSA Identifier GHSA-4gxm-v5v7-fqc4
Severity MODERATE
CVSS v3.1 Base Score 6.5
CWE Classification CWE-73: External Control of File Name or Path
Affected Product pnpm
Package Ecosystem npm
Published Date
Last Updated

Security Impact

CVSS: 6.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction Required (UI:R)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-55699

To remediate this vulnerability, upgrade all affected pnpm dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

pnpm fixed: 10.34.2
npm install pnpm@10.34.2
pnpm fixed: 11.5.3
npm install pnpm@11.5.3
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

npm ls pnpm

How to Check if You Are Affected

To determine whether your Node.js application is affected by CVE-2026-55699, inspect your project dependencies using npm:

npm ls pnpm

Analyze the terminal output to locate any version of pnpm in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 10.34.2), your application is affected.

Note: The npm ls pnpm command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-73: External Control of File Name or Path, where the product allows user input to control or influence paths or file names that are used in filesystem operations. In pnpm, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

<details> <summary>Maintainer Action Plan</summary> ## Maintainer Action Plan This report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path. - Advisory: `CAND-PNPM-085` / `GHSA-4gxm-v5v7-fqc4` - Advisory URL: https://github.com/pnpm/pnpm/security/advisories/GHSA-4gxm-v5v7-fqc4 - Shared patch PR: https://github.com/pnpm/pnpm-ghsa-j2hc-m6cf-6jm8/pull/1 - Shared patch branch: `security/ghsa-batch-2026-06-09` - Patch commit: `a93449314f398cf4bdf2e28d033c02d37395ad22` - Base commit: `origin/main` `55a4035abf1ae3fe7208ba1f5ef43c5eff58ccec` - Maintainer priority: `appendix` - Component: `pnpm global add/remove bin cleanup` - Patch area: bin name/path segment validation - Affected packages: `npm:pnpm` - CWE IDs: `CWE-22`, `CWE-73` - Conservative CVSS: `6.5` / `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H` - Next action: review the shared patch branch for this component, set the final affected version range, merge and release the fix, then publish or close the advisory. ### Expected Patched Behavior Reserved, dot, and path-segment bin names are rejected or ignored; global remove leaves `PNPM_HOME` and the sentinel file intact. ### Files And Tests To Review - `bins/resolver/src/index.ts` - `bins/resolver/test/index.ts` - `global/commands/test/globalRemove.test.ts` - `pacquet/crates/cmd-shim/src/bin_resolver.rs` - `pacquet/crates/cmd-shim/src/bin_resolver/tests.rs` - `.changeset/strange-bin-segments.md` ### Focused Validation Run these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed. - Use the private PR checks plus the patched replay coverage matrix for this candidate. The full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is `results/CAND-PNPM-085-patched-result.json`. <!-- maintainer-action:end --> ## Title Reserved manifest bin names can make global package operations delete outside the global bin directory </details> ## Description ### Summary Manifest `bin` object keys such as `""`, `"."`, and `".."` passed pnpm's bin-name guard. When a malicious package was installed globally, later global remove, update, or add-replacement flows could re-derive those names from the installed manifest and pass `path.join(globalBinDir, binName)` to `removeBin`. For `"."` this targets the global bin directory; for `".."` this targets its parent. ### Details The vulnerable dataflow was: - `bins/resolver/src/index.ts` converted manifest `bin` object keys to `binName` and only required URL-safe text or `$`. Empty, dot, dot-dot, and scoped forms such as `@scope/..` were not rejected after scope stripping. - `global/packages/src/scanGlobalPackages.ts` scanned installed global package manifests and returned manifest-derived `bin.name` values. - `global/commands/src/globalRemove.ts`, `global/commands/src/globalUpdate.ts`, and global add replacement logic joined those names to `globalBinDir`. - `bins/remover/src/removeBins.ts` recursively removed the resulting path. Install-time checks did not close the gap: bin target paths were package-root checked, conflict checks looked at the same escaped path but did not reject reserved segments, and bin-link warning paths could leave the package installed for later global operations. ### PoC Run: The script first performs a safe prepatch simulation in a temporary directory: ```text prepatch_reserved_bin_name=.. prepatch_delete_target=/.../cand-pnpm-085.XXXXXX/home prepatch_deleted_global_bin_parent=true ``` It then validates the patched implementation: ```bash ./node_modules/.bin/tsgo --build bins/resolver/tsconfig.json ./node_modules/.bin/tsgo --build global/commands/tsconfig.json ./node_modules/.bin/eslint bins/resolver/src/index.ts bins/resolver/test/index.ts global/commands/test/globalRemove.test.ts cd bins/resolver NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" ../../node_modules/.bin/jest test/index.ts --runInBand cd global/commands NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" ../../node_modules/.bin/jest test/globalRemove.test.ts -t "global remove ignores reserved manifest bin names" --runInBand cargo fmt --manifest-path pacquet/crates/cmd-shim/Cargo.toml --check cargo test --manifest-path pacquet/crates/cmd-shim/Cargo.toml bin_resolver --lib git diff --check -- bins/resolver global/commands/test/globalRemove.test.ts pacquet/crates/cmd-shim .changeset/strange-bin-segments.md pnpm-lock.yaml ``` The patched resolver no longer emits reserved bin names, and the global-remove regression proves the deletion sink receives only `path.join(globalBinDir, "good")`. ### Impact Direct confidentiality impact was not validated for this primitive; the sink is deletion/corruption, not a read or disclosure path. ## Affected Products Ecosystem: npm Package name: `pnpm` Affected versions: versions before the patch that accept reserved manifest bin names in TypeScript global package flows. Patched versions: pending release containing the shared bin-name hardening. ## Severity Corrected vulnerable severity: High Corrected vulnerable vector string: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H` Corrected vulnerable score: 8.1 Final post-patch score: 0.0, not vulnerable after patch. The original scan score was 8.3 with `C:H/I:H/A:L`. Revalidation removes direct confidentiality impact and raises availability to high because the sink can recursively delete the global bin directory or its parent. ## Weaknesses CWE-22: Improper Limitation of a Pathname to a Restricted Directory CWE-73: External Control of File Name or Path ## Patch - `bins/resolver/src/index.ts` now rejects empty, dot, and dot-dot bin names after scope stripping. - `bins/resolver/test/index.ts` covers empty, dot, dot-dot, and scoped reserved bin keys. - `global/commands/test/globalRemove.test.ts` proves global remove filters reserved manifest bin names before deletion and only removes a safe `good` shim. - `pacquet/crates/cmd-shim/src/bin_resolver.rs` mirrors the same reserved-name rejection; empty names were already rejected. - `pacquet/crates/cmd-shim/src/bin_resolver/tests.rs` extends parity coverage. - `.changeset/strange-bin-segments.md` records patch releases for `@pnpm/bins.resolver`, `pnpm`, and `pacquet`. Pacquet parity is appropriate at the shared bin resolver/linker boundary because pacquet dependency-management commands can resolve and link package bins, even though the TypeScript-only global remove/update/add replacement flow is the concrete destructive-delete sink. ## Validation Passed locally: The script passed TypeScript builds, ESLint, `bins/resolver` Jest, global-remove sink Jest, pacquet fmt/tests, and `git diff --check`.

CVE-2026-55699 Affected pnpm Versions

2 component(s)
Fixed Versions:
10.34.211.5.3
Product / Package Ecosystem Affected Versions Fixed Version
pnpm
npm
< 10.34.2
10.34.2
pnpm
npm
≥ 11.0.0
< 11.5.3
11.5.3

References

Frequently Asked Questions

What is CVE-2026-55699?
CVE-2026-55699 is a security vulnerability in pnpm involving Reserved bin name deletes PNPM_HOME during global remove under CWE-73: External Control of File Name or Path.
Which pnpm versions are affected?
Affected versions include releases prior to 10.34.2, 11.5.3 across affected packages: pnpm.
What version fixes CVE-2026-55699?
Upgrading to 10.34.2, 11.5.3 (or the latest patched release for your respective branch) resolves the vulnerability.
Is pnpm affected?
Yes, pnpm is affected in releases prior to 10.34.2, 11.5.3. Upgrade your dependency to 10.34.2, 11.5.3 or later.
What is the CVSS score of CVE-2026-55699?
CVE-2026-55699 has a CVSS v3.1 base score of 6.5 (MODERATE) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-55699?
CVE-2026-55699 is classified under CWE-73: External Control of File Name or Path (The product allows user input to control or influence paths or file names that are used in filesystem operations.).
How can I check npm dependencies for CVE-2026-55699?
Run the dependency tree command: 'npm ls pnpm'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4gxm-v5v7-fqc4",
  "modified" : "2026-06-26T23:46:53Z",
  "published" : "2026-06-26T23:46:53Z",
  "aliases" : [ "CVE-2026-55699" ],
  "summary" : "pnpm: Reserved bin name deletes PNPM_HOME during global remove",
  "details" : "<details>\n<summary>Maintainer Action Plan</summary>\n\n## Maintainer Action Plan\n\nThis report is ready to review with the shared patch branch. Start with the PR and the expected fixed behavior, then use the detailed exploit narrative below only if you want to replay the original path.\n\n- Advisory: `CAND-PNPM-085` / `GHSA-4gxm-v5v7-fqc4`\n- Advisory URL: https://github.com/pnpm/pnpm/security/advisories/GHSA-4gxm-v5v7-fqc4\n- Shared patch PR: https://github.com/pnpm/pnpm-ghsa-j2hc-m6cf-6jm8/pull/1\n- Shared patch branch: `security/ghsa-batch-2026-06-09`\n- Patch commit: `a93449314f398cf4bdf2e28d033c02d37395ad22`\n- Base commit: `origin/main` `55a4035abf1ae3fe7208ba1f5ef43c5eff58ccec`\n- Maintainer priority: `appendix`\n- Component: `pnpm global add/remove bin cleanup`\n- Patch area: bin name/path segment validation\n- Affected packages: `npm:pnpm`\n- CWE IDs: `CWE-22`, `CWE-73`\n- Conservative CVSS: `6.5` / `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H`\n- Next action: review the shared patch branch for this component, set the final affected version range, merge and release the fix, then publish or close the advisory.\n\n### Expected Patched Behavior\n\nReserved, dot, and path-segment bin names are rejected or ignored; global remove leaves `PNPM_HOME` and the sentinel file intact.\n\n### Files And Tests To Review\n\n- `bins/resolver/src/index.ts`\n- `bins/resolver/test/index.ts`\n- `global/commands/test/globalRemove.test.ts`\n- `pacquet/crates/cmd-shim/src/bin_resolver.rs`\n- `pacquet/crates/cmd-shim/src/bin_resolver/tests.rs`\n- `.changeset/strange-bin-segments.md`\n\n### Focused Validation\n\nRun these from a checkout of the shared patch branch. They are the useful maintainer commands with machine-local artifact paths removed.\n\n- Use the private PR checks plus the patched replay coverage matrix for this candidate.\n\nThe full patched replay for the shared branch passed with all 20 candidates marked fixed. This candidate's replay evidence is `results/CAND-PNPM-085-patched-result.json`.\n<!-- maintainer-action:end -->\n\n## Title\n\nReserved manifest bin names can make global package operations delete outside the global bin directory\n\n</details>\n\n## Description\n\n### Summary\n\nManifest `bin` object keys such as `\"\"`, `\".\"`, and `\"..\"` passed pnpm's bin-name guard. When a malicious package was installed globally, later global remove, update, or add-replacement flows could re-derive those names from the installed manifest and pass `path.join(globalBinDir, binName)` to `removeBin`. For `\".\"` this targets the global bin directory; for `\"..\"` this targets its parent.\n\n### Details\n\nThe vulnerable dataflow was:\n\n- `bins/resolver/src/index.ts` converted manifest `bin` object keys to `binName` and only required URL-safe text or `$`. Empty, dot, dot-dot, and scoped forms such as `@scope/..` were not rejected after scope stripping.\n- `global/packages/src/scanGlobalPackages.ts` scanned installed global package manifests and returned manifest-derived `bin.name` values.\n- `global/commands/src/globalRemove.ts`, `global/commands/src/globalUpdate.ts`, and global add replacement logic joined those names to `globalBinDir`.\n- `bins/remover/src/removeBins.ts` recursively removed the resulting path.\n\nInstall-time checks did not close the gap: bin target paths were package-root checked, conflict checks looked at the same escaped path but did not reject reserved segments, and bin-link warning paths could leave the package installed for later global operations.\n\n### PoC\n\nRun:\n\nThe script first performs a safe prepatch simulation in a temporary directory:\n\n```text\nprepatch_reserved_bin_name=..\nprepatch_delete_target=/.../cand-pnpm-085.XXXXXX/home\nprepatch_deleted_global_bin_parent=true\n```\n\nIt then validates the patched implementation:\n\n```bash\n./node_modules/.bin/tsgo --build bins/resolver/tsconfig.json\n./node_modules/.bin/tsgo --build global/commands/tsconfig.json\n./node_modules/.bin/eslint bins/resolver/src/index.ts bins/resolver/test/index.ts global/commands/test/globalRemove.test.ts\ncd bins/resolver\nNODE_OPTIONS=\"--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" ../../node_modules/.bin/jest test/index.ts --runInBand\ncd global/commands\nNODE_OPTIONS=\"--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" ../../node_modules/.bin/jest test/globalRemove.test.ts -t \"global remove ignores reserved manifest bin names\" --runInBand\ncargo fmt --manifest-path pacquet/crates/cmd-shim/Cargo.toml --check\ncargo test --manifest-path pacquet/crates/cmd-shim/Cargo.toml bin_resolver --lib\ngit diff --check -- bins/resolver global/commands/test/globalRemove.test.ts pacquet/crates/cmd-shim .changeset/strange-bin-segments.md pnpm-lock.yaml\n```\n\nThe patched resolver no longer emits reserved bin names, and the global-remove regression proves the deletion sink receives only `path.join(globalBinDir, \"good\")`.\n\n### Impact\n\nDirect confidentiality impact was not validated for this primitive; the sink is deletion/corruption, not a read or disclosure path.\n\n## Affected Products\n\nEcosystem: npm\n\nPackage name: `pnpm`\n\nAffected versions: versions before the patch that accept reserved manifest bin names in TypeScript global package flows.\n\nPatched versions: pending release containing the shared bin-name hardening.\n\n## Severity\n\nCorrected vulnerable severity: High\n\nCorrected vulnerable vector string: `CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H`\n\nCorrected vulnerable score: 8.1\n\nFinal post-patch score: 0.0, not vulnerable after patch.\n\nThe original scan score was 8.3 with `C:H/I:H/A:L`. Revalidation removes direct confidentiality impact and raises availability to high because the sink can recursively delete the global bin directory or its parent.\n\n## Weaknesses\n\nCWE-22: Improper Limitation of a Pathname to a Restricted Directory\n\nCWE-73: External Control of File Name or Path\n\n## Patch\n\n- `bins/resolver/src/index.ts` now rejects empty, dot, and dot-dot bin names after scope stripping.\n- `bins/resolver/test/index.ts` covers empty, dot, dot-dot, and scoped reserved bin keys.\n- `global/commands/test/globalRemove.test.ts` proves global remove filters reserved manifest bin names before deletion and only removes a safe `good` shim.\n- `pacquet/crates/cmd-shim/src/bin_resolver.rs` mirrors the same reserved-name rejection; empty names were already rejected.\n- `pacquet/crates/cmd-shim/src/bin_resolver/tests.rs` extends parity coverage.\n- `.changeset/strange-bin-segments.md` records patch releases for `@pnpm/bins.resolver`, `pnpm`, and `pacquet`.\n\nPacquet parity is appropriate at the shared bin resolver/linker boundary because pacquet dependency-management commands can resolve and link package bins, even though the TypeScript-only global remove/update/add replacement flow is the concrete destructive-delete sink.\n\n## Validation\n\nPassed locally:\n\nThe script passed TypeScript builds, ESLint, `bins/resolver` Jest, global-remove sink Jest, pacquet fmt/tests, and `git diff --check`.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "npm",
      "name" : "pnpm"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "10.34.2"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "npm",
      "name" : "pnpm"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "11.0.0"
      }, {
        "fixed" : "11.5.3"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/pnpm/pnpm/security/advisories/GHSA-4gxm-v5v7-fqc4"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-55699"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/pnpm/pnpm"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-22", "CWE-73" ],
    "severity" : "MODERATE",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-06-26T23:46:53Z",
    "nvd_published_at" : "2026-06-25T18:16:40Z"
  }
}