From c5c22b6f6b781e2be19af9ce1e34fcf25895415f Mon Sep 17 00:00:00 2001 From: PassionandPeace <3250824815@qq.com> Date: Fri, 28 Aug 2026 23:25:10 +0800 Subject: [PATCH 1/3] fix: filter Node-invalid env keys before fork to utility processes When a parent process has environment variables whose names Node.js treats as invalid (e.g. a leading digit or chars outside [A-Za-z0-9_]), forking a utility process throws 'TypeError: Invalid value for env', breaking the extension host and leaving commands/auth providers unregistered. In createEnv() the environment is built by implicitly inheriting the whole parent process.env (when no explicit env is provided). We strip any key that does not match a valid name before passing it to utilityProcess.fork(). --- .../electron-main/utilityProcess.ts | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index c1dd46b4c1771f..459bbc05e855ea 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -293,14 +293,32 @@ export class UtilityProcess extends Disposable { // Remove any environment variables that are not allowed removeDangerousEnvVariables(env); - // Ensure all values are strings, otherwise the process will not start + // Ensure all values are strings, otherwise the process will not start (and valid keys are forwarded) for (const key of Object.keys(env)) { + if (!UtilityProcess.isValidEnvVariableKey(key)) { + // An environment variable whose name Node.js considers invalid (e.g. leading digit, + // or characters outside [A-Za-z0-9_]) would break the utility process boundary: + // Electron's spawn() throws `TypeError: Invalid value for env`. Such keys leak in + // from the parent `process.env` via implicit (global) inheritance, so we strip them here. + console.warn(`[utilityProcess] Ignoring environment variable with invalid name: '${key}'`); + delete env[key]; + continue; + } env[key] = String(env[key]); } return env; } + /** + * Whether `key` is a name Node.js accepts on process environments. + * Names beginning with a digit or containing characters other than `[A-Za-z0-9_]` + * are rejected and must not be forwarded to child processes. + */ + static isValidEnvVariableKey(key: string): boolean { + return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key); + } + private registerListeners(process: ElectronUtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { // Stdout From a605c93c4c81276207763d674291e1822d158f3b Mon Sep 17 00:00:00 2001 From: PassionandPeace <3250824815@qq.com> Date: Sat, 29 Aug 2026 12:12:12 +0800 Subject: [PATCH 2/3] refactor: extract isValidEnvVariableKey into common envKey module - Move the Node env-key legality predicate out of the UtilityProcess class into src/vs/platform/utilityProcess/common/envKey.ts so it can be shared and unit-tested in isolation. - Add envKey.test.ts covering valid names, leading digits and illegal chars (incl. real-world CJK/CLion/IntelliJ/PATH(x86) cases). - utilityProcess.ts now imports the shared predicate; behavior unchanged (filter Node-invalid keys before fork to avoid 'Invalid value for env'). --- .../utilityProcess/common/envKey.test.ts | 47 +++++++++++++++++++ .../platform/utilityProcess/common/envKey.ts | 16 +++++++ .../electron-main/utilityProcess.ts | 12 +---- 3 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 src/vs/platform/utilityProcess/common/envKey.test.ts create mode 100644 src/vs/platform/utilityProcess/common/envKey.ts diff --git a/src/vs/platform/utilityProcess/common/envKey.test.ts b/src/vs/platform/utilityProcess/common/envKey.test.ts new file mode 100644 index 00000000000000..d23c5792404e22 --- /dev/null +++ b/src/vs/platform/utilityProcess/common/envKey.test.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { isValidEnvVariableKey } from './envKey.js'; + +suite('envKey', () => { + + test('accepts common valid variable names', () => { + assert.strictEqual(isValidEnvVariableKey('PATH'), true); + assert.strictEqual(isValidEnvVariableKey('HOME'), true); + assert.strictEqual(isValidEnvVariableKey('USERNAME'), true); + assert.strictEqual(isValidEnvVariableKey('VSCODE_ESM_ENTRYPOINT'), true); + assert.strictEqual(isValidEnvVariableKey('NODE_UNC_HOST_ALLOWLIST'), true); + assert.strictEqual(isValidEnvVariableKey('_private'), true); + }); + + test('accepts digits after a leading letter/underscore', () => { + assert.strictEqual(isValidEnvVariableKey('a1'), true); + assert.strictEqual(isValidEnvVariableKey('a1b2'), true); + assert.strictEqual(isValidEnvVariableKey('ABC_123'), true); + }); + + test('rejects names starting with a digit', () => { + assert.strictEqual(isValidEnvVariableKey('1'), false); + assert.strictEqual(isValidEnvVariableKey('1HOME'), false); + assert.strictEqual(isValidEnvVariableKey('123'), false); + assert.strictEqual(isValidEnvVariableKey('0a'), false); + }); + + test('rejects names containing illegal characters', () => { + assert.strictEqual(isValidEnvVariableKey('a-b'), false); // hyphen + assert.strictEqual(isValidEnvVariableKey('a b'), false); // space + assert.strictEqual(isValidEnvVariableKey('a+b'), false); // plus + assert.strictEqual(isValidEnvVariableKey('a(b)'), false); // parens + assert.strictEqual(isValidEnvVariableKey('clion_g++'), false); // real-world case + assert.strictEqual(isValidEnvVariableKey('IntelliJ IDEA'), false); + assert.strictEqual(isValidEnvVariableKey('CommonProgramFiles(x86)'), false); + assert.strictEqual(isValidEnvVariableKey('ProgramFiles(x86)'), false); + }); + + test('rejects empty and non-string inputs', () => { + assert.strictEqual(isValidEnvVariableKey(''), false); + }); +}); \ No newline at end of file diff --git a/src/vs/platform/utilityProcess/common/envKey.ts b/src/vs/platform/utilityProcess/common/envKey.ts new file mode 100644 index 00000000000000..8d88705c7dcaec --- /dev/null +++ b/src/vs/platform/utilityProcess/common/envKey.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * Whether `key` is a name Node.js accepts on process environments. + * + * Names beginning with a digit or containing characters other than `[A-Za-z0-9_]` + * are rejected: forwarding such keys to a forked/spawned child environment makes + * Electron throw `TypeError: Invalid value for env`, breaking the child process + * (e.g. the extension host). They must be filtered at the boundary. + */ +export function isValidEnvVariableKey(key: string): boolean { + return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key); +} \ No newline at end of file diff --git a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts index 459bbc05e855ea..612ac9bdaa98b2 100644 --- a/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts +++ b/src/vs/platform/utilityProcess/electron-main/utilityProcess.ts @@ -16,6 +16,7 @@ import { ITelemetryService } from '../../telemetry/common/telemetry.js'; import { ILifecycleMainService } from '../../lifecycle/electron-main/lifecycleMainService.js'; import { removeDangerousEnvVariables } from '../../../base/common/processes.js'; import { deepClone } from '../../../base/common/objects.js'; +import { isValidEnvVariableKey } from '../common/envKey.js'; import { isWindows } from '../../../base/common/platform.js'; import { isUNCAccessRestrictionsDisabled, getUNCHostAllowlist } from '../../../base/node/unc.js'; @@ -295,7 +296,7 @@ export class UtilityProcess extends Disposable { // Ensure all values are strings, otherwise the process will not start (and valid keys are forwarded) for (const key of Object.keys(env)) { - if (!UtilityProcess.isValidEnvVariableKey(key)) { + if (!isValidEnvVariableKey(key)) { // An environment variable whose name Node.js considers invalid (e.g. leading digit, // or characters outside [A-Za-z0-9_]) would break the utility process boundary: // Electron's spawn() throws `TypeError: Invalid value for env`. Such keys leak in @@ -310,15 +311,6 @@ export class UtilityProcess extends Disposable { return env; } - /** - * Whether `key` is a name Node.js accepts on process environments. - * Names beginning with a digit or containing characters other than `[A-Za-z0-9_]` - * are rejected and must not be forwarded to child processes. - */ - static isValidEnvVariableKey(key: string): boolean { - return /^[A-Za-z_][A-Za-z0-9_]*$/.test(key); - } - private registerListeners(process: ElectronUtilityProcess, configuration: IUtilityProcessConfiguration, serviceName: string): void { // Stdout From b857666e6d1ac1828b0d6a8fedce9bba3228b7af Mon Sep 17 00:00:00 2001 From: PassionandPeace <3250824815@qq.com> Date: Sat, 29 Aug 2026 13:30:24 +0800 Subject: [PATCH 3/3] fix: move envKey unit test to test/common per repo convention; add case-variant cases --- .../utilityProcess/{ => test}/common/envKey.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) rename src/vs/platform/utilityProcess/{ => test}/common/envKey.test.ts (85%) diff --git a/src/vs/platform/utilityProcess/common/envKey.test.ts b/src/vs/platform/utilityProcess/test/common/envKey.test.ts similarity index 85% rename from src/vs/platform/utilityProcess/common/envKey.test.ts rename to src/vs/platform/utilityProcess/test/common/envKey.test.ts index d23c5792404e22..980dde10fe4431 100644 --- a/src/vs/platform/utilityProcess/common/envKey.test.ts +++ b/src/vs/platform/utilityProcess/test/common/envKey.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { isValidEnvVariableKey } from './envKey.js'; +import { isValidEnvVariableKey } from '../common/envKey.js'; suite('envKey', () => { @@ -30,15 +30,18 @@ suite('envKey', () => { assert.strictEqual(isValidEnvVariableKey('0a'), false); }); - test('rejects names containing illegal characters', () => { + test('rejects names containing illegal characters, regardless of case', () => { assert.strictEqual(isValidEnvVariableKey('a-b'), false); // hyphen assert.strictEqual(isValidEnvVariableKey('a b'), false); // space assert.strictEqual(isValidEnvVariableKey('a+b'), false); // plus assert.strictEqual(isValidEnvVariableKey('a(b)'), false); // parens assert.strictEqual(isValidEnvVariableKey('clion_g++'), false); // real-world case + assert.strictEqual(isValidEnvVariableKey('CLION_G++'), false); // case variant assert.strictEqual(isValidEnvVariableKey('IntelliJ IDEA'), false); assert.strictEqual(isValidEnvVariableKey('CommonProgramFiles(x86)'), false); assert.strictEqual(isValidEnvVariableKey('ProgramFiles(x86)'), false); + assert.strictEqual(isValidEnvVariableKey('PROGRAMFILES(X86)'), false); + assert.strictEqual(isValidEnvVariableKey('CommonProgramFiles(X86)'), false); }); test('rejects empty and non-string inputs', () => {