src/vs/platform/terminal/node/terminalEnvironment.ts

424 LOC · 280 covered · 144 uncovered · 57 ranges · 2258 concepts · 24 introducers · 1061 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- terminalEnvironment.ts ×6
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import * as os from 'os';
7 > import { FileAccess } from '../../../base/common/network.js';
8 > import * as path from '../../../base/common/path.js';
9 > import { IProcessEnvironment, isMacintosh, isWindows } from '../../../base/common/platform.js';
10 > import * as process from '../../../base/common/process.js';
11 > import { format } from '../../../base/common/strings.js';
12 > import { ILogService } from '../../log/common/log.js';
13 > import { IProductService } from '../../product/common/productService.js';
14 > import { IShellLaunchConfig, ITerminalEnvironment, ITerminalProcessOptions, ShellIntegrationInjectionFailureReason } from '../common/terminal.js';
15 > import { EnvironmentVariableMutatorType } from '../common/environmentVariable.js';
16 > import { deserializeEnvironmentVariableCollections } from '../common/environmentVariableShared.js';
17 > import { MergedEnvironmentVariableCollection } from '../common/environmentVariableCollection.js';
18 > import { chmod, realpathSync, mkdirSync } from 'fs';
19 > import { promisify } from 'util';
20 > import { isString, SingleOrMany } from '../../../base/common/types.js';
21 > import { getWindowsBuildNumberAsync } from '../../../base/node/windowsVersion.js';
22 >
23 > export interface IShellIntegrationConfigInjection {
24 > readonly type: 'injection';
25 > /**
26 > * A new set of arguments to use.
27 > */
28 > readonly newArgs: string[] | undefined;
29 > /**
30 > * An optional environment to mixing to the real environment.
31 > */
32 > readonly envMixin?: IProcessEnvironment;
33 > /**
34 > * An optional array of files to copy from `source` to `dest`.
35 > */
36 > readonly filesToCopy?: {
37 > source: string;
38 > dest: string;
39 > }[];
40 > }
41 >
42 > export interface IShellIntegrationInjectionFailure {
43 > readonly type: 'failure';
44 > readonly reason: ShellIntegrationInjectionFailureReason;
45 > }
46 >
47 > /**
48 > * For a given shell launch config, returns arguments to replace and an optional environment to
49 > * mixin to the SLC's environment to enable shell integration. This must be run within the context
50 > * that creates the process to ensure accuracy. Returns undefined if shell integration cannot be
51 > * enabled.
52 > */
53 > export async function getShellIntegrationInjection( terminalEnvironment.ts ×8
54 > shellLaunchConfig: IShellLaunchConfig,
55 > options: ITerminalProcessOptions,
56 > env: ITerminalEnvironment | undefined,
57 > logService: ILogService,
58 > productService: IProductService,
59 > skipStickyBit: boolean = false
60 > ): Promise<IShellIntegrationConfigInjection | IShellIntegrationInjectionFailure> {
61 > // The global setting is disabled
62 > if (!options.shellIntegration.enabled) {
63 > return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.InjectionSettingDisabled }; terminalEnvironment.ts ×1
64 > }
65 > // There is no executable (so there's no way to determine how to inject) terminalEnvironment.ts ×2
66 > if (!shellLaunchConfig.executable) {
67 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.NoExecutable };
68 }
69 > // It's a feature terminal (tasks, debug), unless it's explicitly being forced terminalEnvironment.ts ×2
70 > if (shellLaunchConfig.isFeatureTerminal && !shellLaunchConfig.forceShellIntegration) { terminalEnvironment.ts ×8
71 > return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.FeatureTerminal }; terminalEnvironment.ts ×1
72 > }
73 > // The ignoreShellIntegration flag is passed (eg. relaunching without shell integration) terminalEnvironment.ts ×6
74 > if (shellLaunchConfig.ignoreShellIntegration) {
75 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.IgnoreShellIntegrationFlag };
76 }
77 > // Shell integration requires Windows 10 build 18309+ (ConPTY support) terminalEnvironment.ts ×6
78 > const windowsBuildNumber = isWindows ? await getWindowsBuildNumberAsync() : 0;
79 > if (isWindows && windowsBuildNumber < 18309) { terminalEnvironment.ts ×8
80 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedWindowsBuild };
81 }
83 > const originalArgs = shellLaunchConfig.args;
84 > const shell = process.platform === 'win32' ? path.basename(shellLaunchConfig.executable).toLowerCase() : path.basename(shellLaunchConfig.executable); terminalEnvironment.ts ×8
85 > const appRoot = path.dirname(FileAccess.asFileUri('').fsPath);
86 > const type = 'injection';
87 > let newArgs: string[] | undefined;
88 > const envMixin: IProcessEnvironment = {
89 > 'VSCODE_INJECTION': '1'
90 > };
91 >
92 > if (options.shellIntegration.nonce) {
93 > envMixin['VSCODE_NONCE'] = options.shellIntegration.nonce; terminalEnvironment.ts ×1
94 > }
95 > // Temporarily pass list of hardcoded env vars for shell env api terminalEnvironment.ts ×6
96 > const scopedDownShellEnvs = ['PATH', 'VIRTUAL_ENV', 'HOME', 'SHELL', 'PWD'];
97 > if (shellLaunchConfig.shellIntegrationEnvironmentReporting) {
98 if (isWindows) {
99 const enableWindowsEnvReporting = options.windowsUseConptyDll || windowsBuildNumber >= 22631 && shell !== 'bash.exe';
100 if (enableWindowsEnvReporting) {
101 envMixin['VSCODE_SHELL_ENV_REPORTING'] = scopedDownShellEnvs.join(',');
102 }
103 } else {
104 envMixin['VSCODE_SHELL_ENV_REPORTING'] = scopedDownShellEnvs.join(',');
105 }
106 }
108 > // Windows
109 > if (isWindows) {
110 if (shell === 'pwsh.exe' || shell === 'powershell.exe') {
111 envMixin['VSCODE_A11Y_MODE'] = options.isScreenReaderOptimized ? '1' : '0';
112
113 if (!originalArgs || arePwshImpliedArgs(originalArgs)) {
114 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.WindowsPwsh);
115 } else if (arePwshLoginArgs(originalArgs)) {
116 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.WindowsPwshLogin);
117 }
118 if (!newArgs) {
119 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
120 }
121 newArgs = [...newArgs];
122 newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
123 envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0';
124 return { type, newArgs, envMixin };
125 } else if (shell === 'bash.exe') {
126 if (!originalArgs || originalArgs.length === 0) {
127 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
128 } else if (areZshBashFishLoginArgs(originalArgs)) {
129 envMixin['VSCODE_SHELL_LOGIN'] = '1';
130 addEnvMixinPathPrefix(options, envMixin, shell);
131 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
132 }
133 if (!newArgs) {
134 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
135 }
136 newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
137 newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
138 envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0';
139 return { type, newArgs, envMixin };
140 }
141 logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args);
142 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedShell };
143 }
145 > // Linux & macOS
146 > switch (shell) {
147 > case 'bash': {
148 > if (!originalArgs || originalArgs.length === 0) { terminalEnvironment.ts ×4
149 > newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash); terminalEnvironment.ts ×1
150 > } else if (areZshBashFishLoginArgs(originalArgs)) { terminalEnvironment.ts ×4
151 > envMixin['VSCODE_SHELL_LOGIN'] = '1'; terminalEnvironment.ts ×1
152 > addEnvMixinPathPrefix(options, envMixin, shell);
153 > newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Bash);
154 > }
155 > if (!newArgs) { terminalEnvironment.ts ×4
156 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
157 }
158 > newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array terminalEnvironment.ts ×4
159 > newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
160 > envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0';
161 > return { type, newArgs, envMixin };
162 > }
163 > case 'fish': { terminalEnvironment.ts ×8
164 if (!originalArgs || originalArgs.length === 0) {
165 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Fish);
166 } else if (areZshBashFishLoginArgs(originalArgs)) {
167 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin);
168 } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Fish) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.FishLogin)) {
169 newArgs = originalArgs;
170 }
171 if (!newArgs) {
172 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
173 }
174
175 // On fish, '$fish_user_paths' is always prepended to the PATH, for both login and non-login shells, so we need
176 // to apply the path prefix fix always, not only for login shells (see #232291)
177 addEnvMixinPathPrefix(options, envMixin, shell);
178
179 newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
180 newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
181 return { type, newArgs, envMixin };
182 }
183 > case 'pwsh': { terminalEnvironment.ts ×8
184 if (!originalArgs || arePwshImpliedArgs(originalArgs)) {
185 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Pwsh);
186 } else if (arePwshLoginArgs(originalArgs)) {
187 newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.PwshLogin);
188 }
189 if (!newArgs) {
190 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
191 }
192 newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array
193 newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot, '');
194 envMixin['VSCODE_STABLE'] = productService.quality === 'stable' ? '1' : '0';
195 return { type, newArgs, envMixin };
196 }
197 > case 'zsh': { terminalEnvironment.ts ×8
198 > if (!originalArgs || originalArgs.length === 0) { terminalEnvironment.ts ×6
199 > newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh); terminalEnvironment.ts ×1
200 > } else if (areZshBashFishLoginArgs(originalArgs)) { terminalEnvironment.ts ×6
201 > newArgs = shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin); terminalEnvironment.ts ×1
202 > addEnvMixinPathPrefix(options, envMixin, shell);
203 > } else if (originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.Zsh) || originalArgs === shellIntegrationArgs.get(ShellIntegrationExecutable.ZshLogin)) {
204 newArgs = originalArgs;
205 }
206 > if (!newArgs) { terminalEnvironment.ts ×6
207 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedArgs };
208 }
209 > newArgs = [...newArgs]; // Shallow clone the array to avoid setting the default array terminalEnvironment.ts ×6
210 > newArgs[newArgs.length - 1] = format(newArgs[newArgs.length - 1], appRoot);
211 >
212 > // Move .zshrc into $ZDOTDIR as the way to activate the script
213 > let username: string;
214 > try {
215 > username = os.userInfo().username;
216 > } catch {
217 username = 'unknown';
218 }
220 > // Resolve the actual tmp directory so we can set the sticky bit
221 > const realTmpDir = realpathSync(os.tmpdir());
222 > const zdotdir = path.join(realTmpDir, `${username}-${productService.applicationName}-zsh`);
223 >
224 > // Set directory permissions using octal notation:
225 > // - 0o1700:
226 > // - Sticky bit is set, preventing non-owners from deleting or renaming files within this directory (1)
227 > // - Owner has full read (4), write (2), execute (1) permissions
228 > // - Group has no permissions (0)
229 > // - Others have no permissions (0)
230 > if (!skipStickyBit) {
231 > // skip for tests agentHostTerminalManager.ts ×5
232 > try {
233 > const chmodAsync = promisify(chmod);
234 > await chmodAsync(zdotdir, 0o1700);
235 > } catch (err) {
236 if (err.message.includes('ENOENT')) {
237 try {
238 mkdirSync(zdotdir);
239 } catch (err) {
240 logService.error(`Failed to create zdotdir at ${zdotdir}: ${err}`);
241 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.FailedToCreateTmpDir };
242 }
243 try {
244 const chmodAsync = promisify(chmod);
245 await chmodAsync(zdotdir, 0o1700);
246 } catch {
247 logService.error(`Failed to set sticky bit on ${zdotdir}: ${err}`);
248 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.FailedToSetStickyBit };
249 }
250 }
251 logService.error(`Failed to set sticky bit on ${zdotdir}: ${err}`);
252 return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.FailedToSetStickyBit };
253 }
255 > envMixin['ZDOTDIR'] = zdotdir; terminalEnvironment.ts ×6
256 > const userZdotdir = env?.ZDOTDIR ?? os.homedir() ?? `~`;
257 > envMixin['USER_ZDOTDIR'] = userZdotdir;
258 > const filesToCopy: IShellIntegrationConfigInjection['filesToCopy'] = [];
259 > filesToCopy.push({
260 > source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh'),
261 > dest: path.join(zdotdir, '.zshrc')
262 > });
263 > filesToCopy.push({
264 > source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh'),
265 > dest: path.join(zdotdir, '.zprofile')
266 > });
267 > filesToCopy.push({
268 > source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh'),
269 > dest: path.join(zdotdir, '.zshenv')
270 > });
271 > filesToCopy.push({
272 > source: path.join(appRoot, 'out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh'),
273 > dest: path.join(zdotdir, '.zlogin')
274 > });
275 > return { type, newArgs, envMixin, filesToCopy };
276 > }
278 > logService.warn(`Shell integration cannot be enabled for executable "${shellLaunchConfig.executable}" and args`, shellLaunchConfig.args); terminalEnvironment.ts ×1
279 > return { type: 'failure', reason: ShellIntegrationInjectionFailureReason.UnsupportedShell };
280 > }
282 > /**
283 > * There are a few situations where some directories are added to the beginning of the PATH.
284 > * 1. On macOS when the profile calls path_helper.
285 > * 2. For fish terminals, which always prepend "$fish_user_paths" to the PATH.
286 > *
287 > * This causes significant problems for the environment variable
288 > * collection API as the custom paths added to the end will now be somewhere in the middle of
289 > * the PATH. To combat this, VSCODE_PATH_PREFIX is used to re-apply any prefix after the profile
290 > * has run. This will cause duplication in the PATH but should fix the issue.
291 > *
292 > * See #99878 for more information.
293 > */
294 > function addEnvMixinPathPrefix(options: ITerminalProcessOptions, envMixin: IProcessEnvironment, shell: string): void { terminalEnvironment.ts ×3
295 > if ((isMacintosh || shell === 'fish') && options.environmentVariableCollections) {
296 // Deserialize and merge
297 const deserialized = deserializeEnvironmentVariableCollections(options.environmentVariableCollections);
298 const merged = new MergedEnvironmentVariableCollection(deserialized);
299
300 // Get all prepend PATH entries
301 const pathEntry = merged.getVariableMap({ workspaceFolder: options.workspaceFolder }).get('PATH');
302 const prependToPath: string[] = [];
303 if (pathEntry) {
304 for (const mutator of pathEntry) {
305 if (mutator.type === EnvironmentVariableMutatorType.Prepend) {
306 prependToPath.push(mutator.value);
307 }
308 }
309 }
310
311 // Add to the environment mixin to be applied in the shell integration script
312 if (prependToPath.length > 0) {
313 envMixin['VSCODE_PATH_PREFIX'] = prependToPath.join('');
314 }
315 }
318 > enum ShellIntegrationExecutable {
319 > WindowsPwsh = 'windows-pwsh',
320 > WindowsPwshLogin = 'windows-pwsh-login',
321 > Pwsh = 'pwsh',
322 > PwshLogin = 'pwsh-login',
323 > Zsh = 'zsh',
324 > ZshLogin = 'zsh-login',
325 > Bash = 'bash',
326 > Fish = 'fish',
327 > FishLogin = 'fish-login',
328 > }
329 >
330 > const shellIntegrationArgs: Map<ShellIntegrationExecutable, string[]> = new Map();
331 > // The try catch swallows execution policy errors in the case of the archive distributable
332 > shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwsh, ['-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.ps1\" } catch {}{1}']);
333 > shellIntegrationArgs.set(ShellIntegrationExecutable.WindowsPwshLogin, ['-l', '-noexit', '-command', 'try { . \"{0}\\out\\vs\\workbench\\contrib\\terminal\\common\\scripts\\shellIntegration.ps1\" } catch {}{1}']);
334 > shellIntegrationArgs.set(ShellIntegrationExecutable.Pwsh, ['-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1"{1}']);
335 > shellIntegrationArgs.set(ShellIntegrationExecutable.PwshLogin, ['-l', '-noexit', '-command', '. "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1"']);
336 > shellIntegrationArgs.set(ShellIntegrationExecutable.Zsh, ['-i']);
337 > shellIntegrationArgs.set(ShellIntegrationExecutable.ZshLogin, ['-il']);
338 > shellIntegrationArgs.set(ShellIntegrationExecutable.Bash, ['--init-file', '{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh']);
339 > shellIntegrationArgs.set(ShellIntegrationExecutable.Fish, ['--init-command', 'source "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish"']);
340 > shellIntegrationArgs.set(ShellIntegrationExecutable.FishLogin, ['-l', '--init-command', 'source "{0}/out/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish"']);
341 > const pwshLoginArgs = ['-login', '-l'];
342 > const shLoginArgs = ['--login', '-l'];
343 > const shInteractiveArgs = ['-i', '--interactive'];
344 > const pwshImpliedArgs = ['-nol', '-nologo'];
345 >
346 function arePwshLoginArgs(originalArgs: SingleOrMany<string>): boolean {
347 if (isString(originalArgs)) {
348 return pwshLoginArgs.includes(originalArgs.toLowerCase());
349 } else {
350 return originalArgs.length === 1 && pwshLoginArgs.includes(originalArgs[0].toLowerCase()) ||
351 (originalArgs.length === 2 &&
352 (((pwshLoginArgs.includes(originalArgs[0].toLowerCase())) || pwshLoginArgs.includes(originalArgs[1].toLowerCase())))
353 && ((pwshImpliedArgs.includes(originalArgs[0].toLowerCase())) || pwshImpliedArgs.includes(originalArgs[1].toLowerCase())));
354 }
355 }
357 function arePwshImpliedArgs(originalArgs: SingleOrMany<string>): boolean {
358 if (isString(originalArgs)) {
359 return pwshImpliedArgs.includes(originalArgs.toLowerCase());
360 } else {
361 return originalArgs.length === 0 || originalArgs?.length === 1 && pwshImpliedArgs.includes(originalArgs[0].toLowerCase());
362 }
363 }
365 > function areZshBashFishLoginArgs(originalArgs: SingleOrMany<string>): boolean { terminalEnvironment.ts ×3
366 > if (!isString(originalArgs)) {
367 > originalArgs = originalArgs.filter(arg => !shInteractiveArgs.includes(arg.toLowerCase()));
368 > }
369 > return isString(originalArgs) && shLoginArgs.includes(originalArgs.toLowerCase())
370 > || !isString(originalArgs) && originalArgs.length === 1 && shLoginArgs.includes(originalArgs[0].toLowerCase());
371 > }
373 > /**
374 > * Patterns that indicate sensitive environment variable names.
375 > */
376 > const sensitiveEnvVarNames = /^(?:.*_)?(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD|CREDENTIAL|AUTH|PRIVATE_?KEY|ACCESS_?KEY|CLIENT_?SECRET|APIKEY)(?:_.*)?$/i;
377 >
378 > /**
379 > * Patterns for detecting secret values in environment variables.
380 > */
381 > const secretValuePatterns = [
382 > // JWT tokens
383 > /^eyJ[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+\.[a-zA-Z0-9\-_]+$/,
384 > // GitHub tokens
385 > /^gh[psuro]_[a-zA-Z0-9]{36}$/,
386 > /^github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}$/,
387 > // Google API keys
388 > /^AIza[A-Za-z0-9_\-]{35}$/,
389 > // Slack tokens
390 > /^xox[pbar]\-[A-Za-z0-9\-]+$/,
391 > // Azure/MS tokens (common patterns)
392 > /^[a-zA-Z0-9]{32,}$/,
393 > ];
394 >
395 > /**
396 > * Sanitizes environment variables for logging by redacting sensitive values.
397 > */
398 > export function sanitizeEnvForLogging(env: IProcessEnvironment | undefined): IProcessEnvironment | undefined {
399 > if (!env) { terminalEnvironment.ts ×1
400 > return env; terminalEnvironment.ts ×1
401 > }
402 > const sanitized: IProcessEnvironment = {}; terminalEnvironment.ts ×2
403 > for (const key of Object.keys(env)) {
404 > const value = env[key]; terminalEnvironment.ts ×3
405 > if (value === undefined) {
406 > continue; terminalEnvironment.ts ×1
407 > }
408 > // Check if the key name suggests a sensitive value terminalEnvironment.ts ×3
409 > if (sensitiveEnvVarNames.test(key)) {
410 > sanitized[key] = '<REDACTED>'; terminalEnvironment.ts ×1
411 > continue;
412 > }
413 > // Check if the value matches known secret patterns terminalEnvironment.ts ×2
414 > let isSecret = false;
415 > for (const pattern of secretValuePatterns) {
416 > if (pattern.test(value)) {
417 > isSecret = true; terminalEnvironment.ts ×1
418 > break;
419 > }
421 > sanitized[key] = isSecret ? '<REDACTED>' : value; terminalEnvironment.ts ×3
422 > }
423 > return sanitized; terminalEnvironment.ts ×2
424 > }