src/vs/platform/sandbox/common/terminalSandboxEngine.ts

1083 LOC · 967 covered · 116 uncovered · 300 ranges · 1052 concepts · 74 introducers · 489 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 > /*--------------------------------------------------------------------------------------------- terminalSandboxEngine.ts ×71
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 { VSBuffer } from '../../../base/common/buffer.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { match as globMatch } from '../../../base/common/glob.js';
9 > import { Disposable } from '../../../base/common/lifecycle.js';
10 > import { posix, win32 } from '../../../base/common/path.js';
11 > import { OperatingSystem, OS } from '../../../base/common/platform.js';
12 > import { arch } from '../../../base/common/process.js';
13 > import { ExtUri } from '../../../base/common/resources.js';
14 > import { URI } from '../../../base/common/uri.js';
15 > import { generateUuid } from '../../../base/common/uuid.js';
16 > import { IFileService } from '../../files/common/files.js';
17 > import { ILogService } from '../../log/common/log.js';
18 > import { matchesDomainPattern, normalizeDomain } from '../../networkFilter/common/domainMatcher.js';
19 > import { AgentNetworkDomainSettingId } from '../../networkFilter/common/settings.js';
20 > import { ISandboxDependencyStatus, type IWindowsMxcConfig, IWindowsMxcFilesystemPolicy, type IWindowsMxcPolicyContainment, type IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
21 > import { AgentSandboxEnabledValue, AgentSandboxSettingId, isAgentSandboxEnabledValue, normalizeAgentSandboxEnabledValue, type AgentSandboxEnabledSettingValue } from './settings.js';
22 > import { IWindowsMxcTerminalSandboxRuntime } from './terminalSandboxMxcRuntime.js';
23 > import { getTerminalSandboxReadAllowListForCommands } from './terminalSandboxReadAllowList.js';
24 > import { getTerminalSandboxRuntimeConfigurationForCommands } from './terminalSandboxRuntimeConfigurationPerOperation.js';
25 > import { ITerminalSandboxCommand, ITerminalSandboxFileAccessCheckResult, ITerminalSandboxPrecheckInputs, ITerminalSandboxPrerequisiteCheckResult, ITerminalSandboxResolvedNetworkDomains, ITerminalSandboxWrapResult, TerminalSandboxFileAccessPermission, TerminalSandboxPrerequisiteCheck, TerminalSandboxPreCheckRemediation } from './terminalSandboxService.js';
26 >
27 > interface ITerminalSandboxFileSystemSetting {
28 > denyRead?: string[];
29 > allowRead?: string[];
30 > allowWrite?: string[];
31 > denyWrite?: string[];
32 > }
33 >
34 > interface ITerminalSandboxFileSystemAccessPaths {
35 > allowReadPaths: string[];
36 > allowWritePaths: string[];
37 > denyReadPaths: string[];
38 > denyWritePaths: string[] | undefined;
39 > }
40 >
41 > /** Runtime information needed to launch the sandbox-runtime CLI. */
42 > export interface ITerminalSandboxRuntimeInfo {
43 > /** Directory that contains `node_modules/@vscode/sandbox-runtime` and `node_modules/@vscode/ripgrep`. */
44 > appRoot: string;
45 > /**
46 > * Name of the directory (relative to {@link appRoot}) that holds the native
47 > * binaries `ripgrep-universal` and `@microsoft/mxc-sdk`. In a packaged desktop
48 > * build these are unpacked from the archive into `node_modules.asar.unpacked`;
49 > * in dev and on remote they live in plain `node_modules`. Defaults to
50 > * `node_modules`. Note the sandbox-runtime CLI itself is always resolved from
51 > * plain `node_modules` (it is duplicated out of the archive) because it is
52 > * spawned as a standalone Node subprocess without the ASAR resolution hook.
53 > */
54 > nativeModulesDir?: string;
55 > /** Path of the node/electron executable used to run sandbox-runtime. */
56 > execPath?: string;
57 > /**
58 > * When true the engine prefixes the wrapped command with `ELECTRON_RUN_AS_NODE=1`
59 > * so the Electron binary acts as a Node.js executable. Set by hosts that resolve
60 > * an Electron-based exec path (the local workbench); leave undefined / false when
61 > * `execPath` already points at a real `node` binary (remote, agent host).
62 > */
63 > runAsNode?: boolean;
64 > /** CPU architecture of the environment that runs the sandbox runtime. */
65 > arch?: string;
66 > }
67 >
68 > /**
69 > * Host adapter that supplies the engine with environment/workspace data the
70 > * platform layer cannot resolve on its own. Hosts (workbench, agent host)
71 > * implement this to bridge their per-environment services (`IRemoteAgentService`,
72 > * `IWorkspaceContextService`, `IEnvironmentService`, `IProductService`,
73 > * `ISandboxHelperService`, …) into the engine.
74 > */
75 > export interface ITerminalSandboxEngineHost {
76 > /** Effective OS used by sandbox decisions. May be the remote OS in workbench. */
77 > getOS(): Promise<OperatingSystem>;
78 > /** Resolves app root + node/electron exec path (after the remote env is known, if applicable). */
79 > getRuntimeInfo(): Promise<ITerminalSandboxRuntimeInfo>;
80 > /** Resolves the user home used for `~`-expansion and the default deny-read entry. */
81 > getUserHome(): Promise<URI | undefined>;
82 > /**
83 > * Resolves the directory the engine creates and uses as its sandbox temp dir
84 > * (sandbox-settings JSON file lives here). May return undefined when no
85 > * suitable location exists, in which case sandboxing is disabled.
86 > */
87 > getSandboxTempDir(): Promise<URI | undefined>;
88 > /** Path added to `allowRead` and `allowWrite` for the engine's workspace/session storage area. */
89 > getWorkspaceStorageReadRoot(): Promise<URI | undefined>;
90 > /** Roots that must be writable inside the sandbox (workspace folders / session cwds). */
91 > getWriteRoots(): readonly URI[];
92 > /** Fires when {@link getWriteRoots} or {@link getWorkspaceStorageReadRoot} change. */
93 > readonly onDidChangeRoots: Event<void>;
94 > /** Resolves the installed sandbox-dependency status (bubblewrap, socat). */
95 > checkSandboxDependencies(): Promise<ISandboxDependencyStatus | undefined>;
96 > /** Resolves host filesystem policy fragments needed by the Windows MXC process container. */
97 > getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy | undefined>;
98 > /** Resolves host environment variables needed by the Windows MXC process container. */
99 > getWindowsMxcEnvironment(): Promise<string[] | undefined>;
100 > /** Builds a Windows MXC payload from a target-environment MXC sandbox policy. */
101 > buildWindowsMxcSandboxPayload(commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined>;
102 > /**
103 > * Returns the effective value of a sandbox-related configuration setting,
104 > * or `undefined` when the setting is not configured. Implementations are
105 > * responsible for mapping deprecated keys to modern ones (the engine
106 > * only ever asks for the modern setting IDs).
107 > */
108 > getSandboxSetting<T>(settingId: string): T | undefined;
109 > /**
110 > * Fires when any value returned by {@link getSandboxSetting} may have
111 > * changed. The engine invalidates its sandbox-config file on each event.
112 > * Implementations should pre-filter to sandbox-relevant keys.
113 > */
114 > readonly onDidChangeSandboxSettings: Event<void>;
115 > }
116 >
117 > /**
118 > * Core sandbox engine. Encapsulates the platform-agnostic logic for wrapping
119 > * commands in a sandbox runtime: enabledness checks, command-line wrapping,
120 > * sandbox-config generation, network-domain extraction and prerequisite checks.
121 > *
122 > * Hosts (workbench / agent host) construct an engine with a host adapter that
123 > * supplies workspace/remote-specific data, then forward their public service
124 > * methods to the engine and add their own host-specific concerns
125 > * (chat elicitation, lifecycle hooks, …) on top.
126 > */
127 > export class TerminalSandboxEngine extends Disposable {
128 > private static readonly _urlRegex = /(?:https?|wss?):\/\/[^\s'"`|&;<>]+/gi;
129 > private static readonly _sshRemoteRegex = /(?:^|[\s'"`])(?:[^\s@:'"`]+@)?([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(?::[^\s'"`|&;<>]+)(?=$|[\s'"`|&;<>])/gi;
130 > private static readonly _hostRegex = /(?:^|[\s'"`(=])([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})(?::\d+)?(?=(?:\/[^\s'"`|&;<>]*)?(?:$|[\s'"`)\]|,;|&<>]))/gi;
131 >
132 > private readonly _sandboxSettingsId: string = generateUuid();
133 > private _runtimeResolved = false;
134 > private _appRoot: string | undefined;
135 > private _execPath: string | undefined;
136 > private _runAsNode = false;
137 > private _userHome: URI | undefined;
138 > private _srtPath: string | undefined;
139 > private _rgPath: string | undefined;
140 > private _mxcPath: string | undefined;
141 > private _windowsMxcFilesystemPolicy: IWindowsMxcFilesystemPolicy | undefined;
142 > private _windowsMxcEnvironment: string[] | undefined;
143 > private _sandboxConfigPath: string | undefined;
144 > private _sandboxDependencyStatus: ISandboxDependencyStatus | undefined;
145 > private _needsForceUpdateConfigFile = true;
146 > private _tempDir: URI | undefined;
147 > private _commandAllowListKeywords: readonly string[] = [];
148 > private _commandAllowListCommandDetails: readonly ITerminalSandboxCommand[] = [];
149 > private _commandCwd: URI | undefined;
150 > private _commandLine: string | undefined;
151 > private _commandShell: string | undefined;
152 > private _commandAllowNetwork = false;
153 > private _os: OperatingSystem = OS;
154 > private readonly _defaultWritePaths: string[] = [];
155 > private readonly _fileSystemPathExtUri = new ExtUri(() => this._os === OperatingSystem.Windows);
156 >
157 > constructor(
158 > private readonly _host: ITerminalSandboxEngineHost, terminalSandboxEngine.ts ×2
159 > @IFileService private readonly _fileService: IFileService,
160 > @ILogService private readonly _logService: ILogService,
161 > @IWindowsMxcTerminalSandboxRuntime private readonly _windowsMxcRuntime: IWindowsMxcTerminalSandboxRuntime,
162 > ) {
163 > super();
164 > this._register(Event.runAndSubscribe(this._host.onDidChangeSandboxSettings, () => {
165 > this.setNeedsForceUpdateConfigFile();
166 > }));
167 > this._register(this._host.onDidChangeRoots(() => this.setNeedsForceUpdateConfigFile()));
168 > }
170 > async isEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
171 > return this._isSandboxConfiguredEnabled(precheckInputs); terminalSandboxEngine.ts ×7
172 > }
174 > async isSandboxAllowNetworkEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
175 > if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) { terminalSandboxEngine.ts ×2
176 > return false; terminalSandboxEngine.ts ×2
177 > }
178 > return this._isSandboxAllowNetworkConfigured(); terminalSandboxEngine.ts ×1
181 > areUnsandboxedCommandsAllowed(): boolean {
182 > return this._areUnsandboxedCommandsAllowed(); terminalSandboxEngine.ts ×1
183 > }
185 > areRetryWithAllowNetworkRequestsAllowed(): boolean {
186 return this._areRetryWithAllowNetworkRequestsAllowed();
187 }
189 > async getOS(): Promise<OperatingSystem> {
190 > this._os = await this._host.getOS(); terminalSandboxEngine.ts ×1
191 > return this._os;
192 > }
194 > getTempDir(): URI | undefined {
195 > return this._tempDir; terminalSandboxEngine.ts ×1
196 > }
198 > setNeedsForceUpdateConfigFile(): void {
199 > this._needsForceUpdateConfigFile = true; terminalSandboxEngine.ts ×2
200 > }
202 > getResolvedNetworkDomains(): ITerminalSandboxResolvedNetworkDomains {
203 > const allowedDomains = this._getSettingValue<string[]>(AgentNetworkDomainSettingId.AllowedNetworkDomains) ?? []; terminalSandboxEngine.ts ×1
204 > const deniedDomains = this._getSettingValue<string[]>(AgentNetworkDomainSettingId.DeniedNetworkDomains) ?? [];
205 > return { allowedDomains, deniedDomains };
206 > }
208 > async wrapCommand(command: string, requestUnsandboxedExecution?: boolean, shell?: string, cwd?: URI, commandDetails?: readonly ITerminalSandboxCommand[], requestAllowNetwork?: boolean): Promise<ITerminalSandboxWrapResult> {
209 > const allowUnsandboxedCommands = this._areUnsandboxedCommandsAllowed(); terminalSandboxEngine.ts ×10
210 > const retryWithAllowNetworkRequests = this._areRetryWithAllowNetworkRequestsAllowed();
211 > const shouldInspectBlockedDomains = requestUnsandboxedExecution !== true && requestAllowNetwork !== true && (retryWithAllowNetworkRequests || allowUnsandboxedCommands);
212 > const blockedDomainResult = shouldInspectBlockedDomains ? this._getBlockedDomains(command) : { blockedDomains: [], deniedDomains: [] };
213 > const requiresPreflightAllowNetwork = retryWithAllowNetworkRequests && blockedDomainResult.blockedDomains.length > 0;
214 > const allowNetworkForCommand = requestUnsandboxedExecution !== true && ((requestAllowNetwork === true && retryWithAllowNetworkRequests) || requiresPreflightAllowNetwork);
215 > const normalizedCommandDetails = this._normalizeCommandDetails(commandDetails ?? []);
216 > const normalizedCommandKeywords = this._normalizeCommandKeywords(normalizedCommandDetails.map(c => c.keyword));
217 > const currentReadAllowListPaths = getTerminalSandboxReadAllowListForCommands(this._os, this._commandAllowListKeywords, this._commandAllowListCommandDetails);
218 > const nextReadAllowListPaths = getTerminalSandboxReadAllowListForCommands(this._os, normalizedCommandKeywords, normalizedCommandDetails);
219 > const currentRuntimeConfiguration = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
220 > const nextRuntimeConfiguration = getTerminalSandboxRuntimeConfigurationForCommands(this._os, normalizedCommandDetails);
221 > const shouldRefreshConfig = this._commandAllowListKeywords.length === 0
222 || this._needsForceUpdateConfigFile
223 || !this._areStringArraysEqual(this._commandAllowListKeywords, normalizedCommandKeywords)
224 || !this._areStringArraysEqual(currentReadAllowListPaths, nextReadAllowListPaths)
225 || !this._areObjectsEqual(currentRuntimeConfiguration, nextRuntimeConfiguration)
226 || this._commandCwd?.toString() !== cwd?.toString()
227 || this._commandAllowNetwork !== allowNetworkForCommand
228 || (this._os === OperatingSystem.Windows && (this._commandLine !== command || this._commandShell !== shell));
229 > if (shouldRefreshConfig) { terminalSandboxEngine.ts ×10
230 > this._commandAllowListKeywords = normalizedCommandKeywords;
231 > this._commandAllowListCommandDetails = normalizedCommandDetails;
232 > this._commandCwd = cwd;
233 > this._commandLine = command;
234 > this._commandShell = shell;
235 > this._commandAllowNetwork = allowNetworkForCommand;
236 > await this.getSandboxConfigPath(true);
237 > }
238 >
239 > if (!this._sandboxConfigPath || !this._tempDir) {
240 throw new Error('Sandbox config path or temp dir not initialized');
241 }
243 > // If per-command network relaxation is disabled, preserve the existing
244 > // unsandbox fallback for commands with statically-detected blocked domains.
245 > if (!requestUnsandboxedExecution && !retryWithAllowNetworkRequests && allowUnsandboxedCommands && blockedDomainResult.blockedDomains.length > 0) {
246 > return { copilotShellTools.ts ×3
247 > command: this._wrapUnsandboxedCommand(command, shell),
248 > isSandboxWrapped: false,
249 > blockedDomains: blockedDomainResult.blockedDomains,
250 > deniedDomains: blockedDomainResult.deniedDomains,
251 > requiresUnsandboxConfirmation: true,
252 > };
253 > }
255 > // If requestUnsandboxedExecution is true, need to ensure env variables set during sandbox still apply.
256 > if (requestUnsandboxedExecution && allowUnsandboxedCommands) { terminalSandboxEngine.ts ×10
258 > command: this._wrapUnsandboxedCommand(command, shell),
259 > isSandboxWrapped: false,
260 > };
261 > }
263 > const allowNetworkConfirmationMetadata = requiresPreflightAllowNetwork ? {
264 > blockedDomains: blockedDomainResult.blockedDomains, terminalSandboxEngine.ts ×2
265 > deniedDomains: blockedDomainResult.deniedDomains,
266 > } : undefined; terminalSandboxEngine.ts ×10
267 >
268 > if (this._os === OperatingSystem.Windows) {
269 > if (!this._mxcPath) { terminalSandboxMxcRuntime.ts ×11
270 throw new Error('MXC executable path not resolved');
271 }
273 > command: this._windowsMxcRuntime.wrapCommand(this._mxcPath, this._sandboxConfigPath),
274 > isSandboxWrapped: true,
275 > requiresAllowNetworkConfirmation: allowNetworkForCommand && !this._isSandboxAllowNetworkConfigured() ? true : undefined,
276 > ...allowNetworkConfirmationMetadata,
277 > };
278 > }
280 > if (!this._execPath) {
281 throw new Error('Executable path not set to run sandbox commands');
282 }
283 > if (!this._srtPath) { terminalSandboxEngine.ts ×8
284 throw new Error('Sandbox runtime path not resolved');
285 }
286 > if (!this._rgPath) { terminalSandboxEngine.ts ×8
287 throw new Error('Ripgrep path not resolved');
288 }
289 > // Use ELECTRON_RUN_AS_NODE=1 to make Electron executable behave as Node.js terminalSandboxEngine.ts ×8
290 > // TMPDIR must be set as environment variable before the command
291 > // Quote shell arguments so the wrapped command cannot break out of the outer shell.
292 > const commandToRunInSandbox = this._getSandboxCommandWithPreservedCwd(command, cwd);
293 > const sandboxRuntimeCommand = `PATH="$PATH:${this._pathDirname(this._rgPath)}" TMPDIR="${this._tempDir.path}" CLAUDE_TMPDIR="${this._tempDir.path}" "${this._execPath}" "${this._srtPath}" --settings "${this._sandboxConfigPath}" -c ${this._quoteShellArgument(commandToRunInSandbox)}`;
294 > // On workbench Electron builds the exec path points at the Electron binary, so we
295 > // prefix `ELECTRON_RUN_AS_NODE=1` to make it behave as Node.js. Remote workbench and
296 > // the agent host already resolve a real `node` binary and the host clears the flag.
297 > if (this._runAsNode) {
298 > const nodeSandboxRuntimeCommand = `ELECTRON_RUN_AS_NODE=1 ${sandboxRuntimeCommand}`; terminalSandboxEngine.ts ×1
299 > return {
300 > command: this._wrapSandboxRuntimeCommandForLaunch(nodeSandboxRuntimeCommand, cwd),
301 > isSandboxWrapped: true,
302 > requiresAllowNetworkConfirmation: allowNetworkForCommand && !this._isSandboxAllowNetworkConfigured() ? true : undefined,
303 > ...allowNetworkConfirmationMetadata,
304 > };
305 > }
307 > command: this._wrapSandboxRuntimeCommandForLaunch(sandboxRuntimeCommand, cwd),
308 > isSandboxWrapped: true,
309 > requiresAllowNetworkConfirmation: allowNetworkForCommand && !this._isSandboxAllowNetworkConfigured() ? true : undefined, terminalSandboxEngine.ts ×10
310 > ...allowNetworkConfirmationMetadata,
311 > };
312 > }
314 > async checkForSandboxingPrereqs(forceRefresh: boolean = false, precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxPrerequisiteCheckResult> {
315 > if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) { terminalSandboxEngine.ts ×2
317 > enabled: false,
318 > sandboxConfigPath: undefined,
319 > failedCheck: undefined,
320 > };
321 > }
323 > const sandboxConfigPath = await this.getSandboxConfigPath(forceRefresh, precheckInputs);
324 > if (!sandboxConfigPath) {
325 return {
326 enabled: true,
327 sandboxConfigPath,
328 failedCheck: TerminalSandboxPrerequisiteCheck.Config,
329 };
330 }
332 > if (!(await this._checkSandboxDependencies(forceRefresh))) {
333 > const missingDependencies = await this.getMissingSandboxDependencies();
334 > if (missingDependencies.length === 0 && this._sandboxDependencyStatus?.bubblewrapInstalled && !this._sandboxDependencyStatus.bubblewrapUsable) {
336 > enabled: true,
337 > sandboxConfigPath,
338 > failedCheck: TerminalSandboxPrerequisiteCheck.Bubblewrap,
339 > remediations: this._getBubblewrapRemediations(),
340 > detail: this._sandboxDependencyStatus.bubblewrapError,
341 > };
342 > }
344 > enabled: true,
345 > sandboxConfigPath,
346 > failedCheck: TerminalSandboxPrerequisiteCheck.Dependencies,
347 > missingDependencies,
348 > canInstallMissingDependencies: !!this._sandboxDependencyStatus?.dependencyInstallCommand,
350 > }
352 > return {
353 > enabled: true,
354 > sandboxConfigPath,
355 > failedCheck: undefined,
356 > };
359 > async checkFileAccess(permission: TerminalSandboxFileAccessPermission, paths: readonly string[], precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxFileAccessCheckResult> {
360 > if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) { terminalSandboxEngine.ts ×19
361 return { allowed: true, denied: [] };
362 }
364 > await this._resolveRuntimeInfo();
365 > if (!this._tempDir) {
366 > await this._initTempDir();
367 > }
368 >
369 > const configFilePath = this._tempDir ? this._getUriPath(URI.joinPath(this._tempDir, `vscode-sandbox-settings-${this._sandboxSettingsId}.json`)) : undefined;
370 > const accessPaths = await this._getFileSystemAccessPaths(configFilePath);
371 > const denied: string[] = [];
372 > for (const path of paths) {
373 > if (!path || !await this._hasFileSystemAccess(permission, path, accessPaths)) {
374 > denied.push(path); terminalSandboxEngine.ts ×1
375 > }
377 >
378 > return { allowed: denied.length === 0, denied };
379 > }
381 > async getSandboxConfigPath(forceRefresh: boolean = false, precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<string | undefined> {
382 > if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) { terminalSandboxEngine.ts ×3
383 > return undefined; terminalSandboxEngine.ts ×2
384 > }
385 > await this._resolveRuntimeInfo(); terminalSandboxEngine.ts ×13
386 > if (!this._sandboxConfigPath || forceRefresh || this._needsForceUpdateConfigFile) { terminalSandboxEngine.ts ×3
387 > this._sandboxConfigPath = await this._createSandboxConfig(); terminalSandboxEngine.ts ×13
388 > this._needsForceUpdateConfigFile = false;
389 > }
390 > return this._sandboxConfigPath;
393 > async getMissingSandboxDependencies(): Promise<string[]> {
394 > const os = await this.getOS(); terminalSandboxEngine.ts ×14
395 > if (os === OperatingSystem.Windows) {
396 return [];
397 }
399 > if (!this._sandboxDependencyStatus) {
400 this._sandboxDependencyStatus = await this._host.checkSandboxDependencies();
401 }
403 > const missing: string[] = [];
404 > if (this._sandboxDependencyStatus && !this._sandboxDependencyStatus.bubblewrapInstalled) {
405 > missing.push('bubblewrap'); terminalSandboxEngine.ts ×4
406 > }
407 > if (this._sandboxDependencyStatus && !this._sandboxDependencyStatus.socatInstalled) { terminalSandboxEngine.ts ×14
408 missing.push('socat');
409 }
410 > return missing; terminalSandboxEngine.ts ×14
411 > }
413 > /**
414 > * Deletes the sandbox temp directory if one was created. Hosts are expected
415 > * to invoke this from their shutdown / disposal path; the engine itself does
416 > * not delete the directory on `dispose()` because shutdown joiners need to
417 > * be coordinated externally.
418 > */
419 > async cleanupTempDir(): Promise<void> {
420 > if (!this._tempDir) { terminalSandboxEngine.ts ×2
422 > }
424 > await this._fileService.del(this._tempDir, { recursive: true, useTrash: false });
425 > } catch (error) {
426 this._logService.warn('TerminalSandboxEngine: Failed to delete sandbox temp dir', error);
427 }
430 > // ---- private helpers ----------------------------------------------------
431 >
432 > private async _checkSandboxDependencies(forceRefresh = false): Promise<boolean> {
433 > const os = await this.getOS(); terminalSandboxEngine.ts ×14
434 > if (os === OperatingSystem.Windows) {
435 return true;
436 }
438 > if (!forceRefresh && this._sandboxDependencyStatus) {
439 > return this._sandboxDependencyStatus.bubblewrapInstalled && this._sandboxDependencyStatus.bubblewrapUsable && this._sandboxDependencyStatus.socatInstalled; terminalSandboxEngine.ts ×1
440 > }
442 > const status = await this._host.checkSandboxDependencies();
443 > this._sandboxDependencyStatus = status;
444 >
445 > if (status && !status.bubblewrapInstalled) {
446 > this._logService.warn('TerminalSandboxEngine: bubblewrap (bwrap) is not installed'); terminalSandboxEngine.ts ×4
447 > } else if (status && !status.bubblewrapUsable) { terminalSandboxEngine.ts ×14
448 > this._logService.warn('TerminalSandboxEngine: bubblewrap (bwrap) is installed but failed its capability check', status.bubblewrapError); terminalSandboxEngine.ts ×3
449 > }
450 > if (status && !status.socatInstalled) { terminalSandboxEngine.ts ×14
451 this._logService.warn('TerminalSandboxEngine: socat is not installed');
452 }
454 > return status ? status.bubblewrapInstalled && status.bubblewrapUsable && status.socatInstalled : true;
455 > }
457 > private _getBubblewrapRemediations(): readonly TerminalSandboxPreCheckRemediation[] | undefined {
458 > return [TerminalSandboxPreCheckRemediation.DisableUnprivilagedusernamespaceRestriction]; terminalSandboxEngine.ts ×3
459 > }
461 > private _quoteShellArgument(value: string): string {
462 > return `'${value.replace(/'/g, `'\\''`)}'`; terminalSandboxEngine.ts ×1
463 > }
465 > private _getSandboxCommandWithPreservedCwd(command: string, cwd: URI | undefined): string {
466 > if (this._os !== OperatingSystem.Linux || !cwd?.path || cwd.path === this._tempDir?.path) { terminalSandboxEngine.ts ×8
467 > return command; terminalSandboxEngine.ts ×2
468 > }
469 > return `cd ${this._quoteShellArgument(cwd.path)} && ${command}`; terminalSandboxEngine.ts ×2
472 > private _wrapSandboxRuntimeCommandForLaunch(sandboxRuntimeCommand: string, cwd: URI | undefined): string {
473 > const tempDirPath = this._tempDir?.path; terminalSandboxEngine.ts ×8
474 > return this._os === OperatingSystem.Linux && cwd?.path && tempDirPath && cwd.path !== tempDirPath
475 > ? `cd ${this._quoteShellArgument(tempDirPath)}; ${sandboxRuntimeCommand}` terminalSandboxEngine.ts ×2
476 > : sandboxRuntimeCommand; terminalSandboxEngine.ts ×2
479 > private _wrapUnsandboxedCommand(command: string, shell?: string): string {
480 > if (this._os === OperatingSystem.Windows) { terminalSandboxEngine.ts ×4
481 return this._windowsMxcRuntime.wrapUnsandboxedCommand(command);
482 }
483 > if (!this._tempDir?.path) { terminalSandboxEngine.ts ×4
484 return command;
485 }
486 > if (!shell) { terminalSandboxEngine.ts ×4
487 return `(TMPDIR="${this._tempDir.path}"; export TMPDIR; ${command})`;
488 }
489 > return `env TMPDIR="${this._tempDir.path}" ${this._quoteShellArgument(shell)} -c ${this._quoteShellArgument(command)}`; terminalSandboxEngine.ts ×4
490 > }
492 > private _getBlockedDomains(command: string): { blockedDomains: string[]; deniedDomains: string[] } {
493 > if (this._isSandboxAllowNetworkConfigured()) { terminalSandboxEngine.ts ×2
494 > return { blockedDomains: [], deniedDomains: [] }; terminalSandboxMxcRuntime.ts ×11
495 > }
497 > const domains = this._extractDomains(command);
498 > if (domains.length === 0) {
499 > return { blockedDomains: [], deniedDomains: [] }; terminalSandboxEngine.ts ×1
500 > }
502 > const { allowedDomains, deniedDomains } = this.getResolvedNetworkDomains();
503 > const blockedDomains = new Set<string>();
504 > const explicitlyDeniedDomains = new Set<string>();
505 > for (const domain of domains) {
506 > if (deniedDomains.some(pattern => matchesDomainPattern(domain, pattern))) {
507 > blockedDomains.add(domain); terminalSandboxEngine.ts ×2
508 > explicitlyDeniedDomains.add(domain);
509 > continue;
510 > }
511 > if (!allowedDomains.some(pattern => matchesDomainPattern(domain, pattern))) { copilotShellTools.ts ×3
512 > blockedDomains.add(domain);
513 > }
515 > return {
516 > blockedDomains: [...blockedDomains],
517 > deniedDomains: [...explicitlyDeniedDomains],
518 > };
521 > private _extractDomains(command: string): string[] {
522 > const domains = new Set<string>(); terminalSandboxEngine.ts ×5
523 > let match: RegExpExecArray | null;
524 >
525 > TerminalSandboxEngine._urlRegex.lastIndex = 0;
526 > while ((match = TerminalSandboxEngine._urlRegex.exec(command)) !== null) {
527 > const domain = this._extractDomainFromUrl(match[0]); terminalSandboxEngine.ts ×5
528 > if (domain) {
529 > domains.add(domain);
530 > }
531 > }
533 > TerminalSandboxEngine._sshRemoteRegex.lastIndex = 0;
534 > while ((match = TerminalSandboxEngine._sshRemoteRegex.exec(command)) !== null) {
535 const domain = normalizeDomain(match[1], true);
536 if (domain) {
537 domains.add(domain);
538 }
539 }
541 > TerminalSandboxEngine._hostRegex.lastIndex = 0;
542 > while ((match = TerminalSandboxEngine._hostRegex.exec(command)) !== null) {
543 const domain = normalizeDomain(match[1]);
544 if (domain) {
545 domains.add(domain);
546 }
547 }
549 > return [...domains];
550 > }
552 > private _extractDomainFromUrl(value: string): string | undefined {
554 > const authority = URI.parse(value).authority;
555 > return normalizeDomain(authority, true);
556 > } catch {
557 return undefined;
558 }
561 > private _normalizeCommandKeywords(commandKeywords: readonly string[]): string[] {
562 > return [...new Set(commandKeywords.map(keyword => keyword.toLowerCase()))].sort(); terminalSandboxEngine.ts ×10
563 > }
565 > private _normalizeCommandDetails(commandDetails: readonly ITerminalSandboxCommand[]): ITerminalSandboxCommand[] {
566 > const seen = new Set<string>(); terminalSandboxEngine.ts ×10
567 > const result: ITerminalSandboxCommand[] = [];
568 > for (const command of commandDetails) {
569 > const normalizedCommand = { keyword: command.keyword.toLowerCase(), args: [...command.args] }; terminalSandboxReadAllowList.ts ×15
570 > const key = JSON.stringify(normalizedCommand);
571 > if (!seen.has(key)) {
572 > seen.add(key);
573 > result.push(normalizedCommand);
574 > }
575 > }
576 > return result.sort((a, b) => a.keyword.localeCompare(b.keyword) || a.args.join('\0').localeCompare(b.args.join('\0'))); terminalSandboxEngine.ts ×10
577 > }
579 > private _areStringArraysEqual(a: readonly string[], b: readonly string[]): boolean {
580 return a.length === b.length && a.every((keyword, index) => keyword === b[index]);
581 }
583 > private _areObjectsEqual(a: Record<string, unknown>, b: Record<string, unknown>): boolean {
584 return JSON.stringify(a) === JSON.stringify(b);
585 }
587 > private _isSandboxAllowedByPrecheckInputs(precheckInputs: ITerminalSandboxPrecheckInputs | undefined): boolean {
588 > return precheckInputs?.isDefaultApprovalPermissionEnabled !== false; terminalSandboxEngine.ts ×7
589 > }
591 > private async _isSandboxConfiguredEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
592 > if (!this._isSandboxAllowedByPrecheckInputs(precheckInputs)) { terminalSandboxEngine.ts ×7
593 > return false; terminalSandboxEngine.ts ×2
594 > }
595 > await this.getOS(); terminalSandboxEngine.ts ×7
596 > if (this._os === OperatingSystem.Windows) {
597 > const value = this._getSandboxConfiguredWindowsEnabledValue(); terminalSandboxEngine.ts ×2
598 > return isAgentSandboxEnabledValue(value);
599 > }
600 > const value = this._getSandboxConfiguredEnabledValue(); terminalSandboxEngine.ts ×2
601 > return isAgentSandboxEnabledValue(value);
604 > private async _resolveRuntimeInfo(): Promise<void> {
605 > if (this._runtimeResolved) { terminalSandboxEngine.ts ×11
607 > }
608 > this._runtimeResolved = true; terminalSandboxEngine.ts ×11
609 > const runtimeInfo = await this._host.getRuntimeInfo();
610 > this._appRoot = runtimeInfo.appRoot;
611 > this._execPath = runtimeInfo.execPath;
612 > this._runAsNode = runtimeInfo.runAsNode ?? false;
613 > this._userHome = await this._host.getUserHome();
614 > this._srtPath = this._pathJoin(this._appRoot, 'node_modules', '@vscode', 'sandbox-runtime', 'dist', 'cli.js');
615 > const nativeModulesDir = runtimeInfo.nativeModulesDir ?? 'node_modules';
616 > const rgPlatform = this._os === OperatingSystem.Windows ? 'win32' : this._os === OperatingSystem.Macintosh ? 'darwin' : 'linux';
617 > const rgBinary = this._os === OperatingSystem.Windows ? 'rg.exe' : 'rg';
618 > this._rgPath = this._pathJoin(this._appRoot, nativeModulesDir, '@vscode', 'ripgrep-universal', 'bin', `${rgPlatform}-${arch}`, rgBinary);
619 > this._mxcPath = this._windowsMxcRuntime.getExecutablePath(this._appRoot, nativeModulesDir, runtimeInfo.arch);
620 > }
622 > private async _createSandboxConfig(): Promise<string | undefined> {
623 > if ((await this.isEnabled()) && !this._tempDir) { terminalSandboxEngine.ts ×13
624 > await this._initTempDir();
625 > }
626 > if (!this._tempDir) {
627 return undefined;
628 }
630 > const allowNetwork = this._commandAllowNetwork || await this.isSandboxAllowNetworkEnabled();
631 > const linuxFileSystemSetting = this._os === OperatingSystem.Linux terminalSandboxEngine.ts ×1
632 > ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxLinuxFileSystem) ?? {} terminalSandboxEngine.ts ×2
634 > const macFileSystemSetting = this._os === OperatingSystem.Macintosh terminalSandboxEngine.ts ×13
635 > ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxMacFileSystem) ?? {} terminalSandboxEngine.ts ×3
637 > const windowsFileSystemSetting = this._os === OperatingSystem.Windows terminalSandboxEngine.ts ×13
638 > ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxWindowsFileSystem) ?? {} terminalSandboxMxcRuntime.ts ×11
640 > const windowsSchemaVersion = this._os === OperatingSystem.Windows terminalSandboxEngine.ts ×13
641 > ? this._getSettingValue<string>(AgentSandboxSettingId.AgentSandboxWindowsSchemaVersion) terminalSandboxMxcRuntime.ts ×11
642 > : undefined; terminalSandboxEngine.ts ×8
643 > const runtimeSetting = this._getSettingValue<Record<string, unknown>>(AgentSandboxSettingId.AgentSandboxAdvancedRuntime) ?? {}; terminalSandboxEngine.ts ×13
644 > const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
645 > const commandRuntimeAllowReadPaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowRead');
646 > const commandRuntimeAllowWritePaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowWrite');
647 > const configFileUri = URI.joinPath(this._tempDir, `vscode-sandbox-settings-${this._sandboxSettingsId}.json`);
648 > const configFilePath = this._getUriPath(configFileUri);
649 > let allowWritePaths: string[] = [];
650 > let allowReadPaths: string[] = [];
651 > let denyReadPaths: string[] = [];
652 > let denyWritePaths: string[] | undefined;
653 > if (this._os === OperatingSystem.Windows) {
654 > const filesystemPolicy = await this._getWindowsMxcFilesystemPolicy(); terminalSandboxMxcRuntime.ts ×11
655 > const env = await this._getWindowsMxcEnvironment();
656 > allowWritePaths = await this._resolveFileSystemPaths([
657 > ...await this._updateAllowWritePathsWithWorkspaceFolders(windowsFileSystemSetting.allowWrite),
658 > ...filesystemPolicy.readwritePaths
659 > ]);
660 > allowReadPaths = await this._resolveFileSystemPaths([...(windowsFileSystemSetting.allowRead ?? []), ...filesystemPolicy.readonlyPaths]);
661 > denyReadPaths = await this._resolveFileSystemPaths(windowsFileSystemSetting.denyRead ?? []);
662 > this._windowsMxcEnvironment = env;
663 > } else if (this._os === OperatingSystem.Macintosh) { terminalSandboxEngine.ts ×13
664 > allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(macFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath); terminalSandboxEngine.ts ×3
665 > allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(macFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
666 > denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(macFileSystemSetting.denyRead ?? []), configFilePath]));
667 > denyWritePaths = macFileSystemSetting.denyWrite ? await this._resolveFileSystemPaths(macFileSystemSetting.denyWrite) : undefined;
668 > } else if (this._os === OperatingSystem.Linux) { terminalSandboxEngine.ts ×8
669 > allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(linuxFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath); terminalSandboxEngine.ts ×2
670 > allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(linuxFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
671 > denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(linuxFileSystemSetting.denyRead ?? []), configFilePath]));
672 > denyWritePaths = await this._resolveFileSystemPaths(linuxFileSystemSetting.denyWrite);
673 > }
674 > const sandboxSettings = this._os === OperatingSystem.Windows ? await this._windowsMxcRuntime.createConfig({ terminalSandboxEngine.ts ×13
675 > command: this._commandLine ?? '', terminalSandboxMxcRuntime.ts ×11
676 > shell: this._commandShell,
677 > cwd: this._commandCwd ?? this._getDefaultWindowsMxcCwd(),
678 > tempDir: this._tempDir,
679 > schemaVersion: windowsSchemaVersion,
680 > allowNetwork,
681 > allowReadPaths,
682 > allowWritePaths,
683 > denyReadPaths,
684 > env: this._windowsMxcEnvironment ?? [],
685 > }, this._buildSandboxPayload) : { terminalSandboxEngine.ts ×13
686 > network: allowNetwork ? { allowedDomains: [], deniedDomains: [], enabled: false } : this.getResolvedNetworkDomains(), terminalSandboxEngine.ts ×8
687 > filesystem: {
688 > denyRead: denyReadPaths,
689 > allowRead: allowReadPaths,
690 > allowWrite: allowWritePaths,
691 > denyWrite: denyWritePaths,
692 > },
693 > };
694 > if (this._os !== OperatingSystem.Windows) { terminalSandboxEngine.ts ×13
695 > const sandboxRuntimeSettings = sandboxSettings as Record<string, unknown>; terminalSandboxEngine.ts ×8
696 > this._mergeAdditionalSandboxConfigProperties(sandboxRuntimeSettings, runtimeSetting);
697 > this._mergeAdditionalSandboxConfigProperties(sandboxRuntimeSettings, commandRuntimeSetting);
698 > if (this._os === OperatingSystem.Macintosh) {
699 > sandboxRuntimeSettings.allowPty ??= true; terminalSandboxEngine.ts ×3
700 > }
702 > this._sandboxConfigPath = configFilePath; terminalSandboxEngine.ts ×13
703 > await this._fileService.createFile(configFileUri, VSBuffer.fromString(JSON.stringify(sandboxSettings, null, '\t')), { overwrite: true });
704 > return this._sandboxConfigPath;
705 > }
707 > private async _getFileSystemAccessPaths(configFilePath: string | undefined): Promise<ITerminalSandboxFileSystemAccessPaths> {
708 > const linuxFileSystemSetting = this._os === OperatingSystem.Linux terminalSandboxEngine.ts ×19
709 > ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxLinuxFileSystem) ?? {}
710 : {};
711 > const macFileSystemSetting = this._os === OperatingSystem.Macintosh terminalSandboxEngine.ts ×19
712 ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxMacFileSystem) ?? {}
714 > const windowsFileSystemSetting = this._os === OperatingSystem.Windows
715 ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxWindowsFileSystem) ?? {}
717 > const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
718 > const commandRuntimeAllowReadPaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowRead');
719 > const commandRuntimeAllowWritePaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowWrite');
720 > let allowWritePaths: string[] = [];
721 > let allowReadPaths: string[] = [];
722 > let denyReadPaths: string[] = [];
723 > let denyWritePaths: string[] | undefined;
724 > if (this._os === OperatingSystem.Windows) {
725 const filesystemPolicy = await this._getWindowsMxcFilesystemPolicy();
726 allowWritePaths = await this._resolveFileSystemPaths([
727 ...await this._updateAllowWritePathsWithWorkspaceFolders(windowsFileSystemSetting.allowWrite),
728 ...filesystemPolicy.readwritePaths
729 ]);
730 allowReadPaths = await this._resolveFileSystemPaths([...(windowsFileSystemSetting.allowRead ?? []), ...filesystemPolicy.readonlyPaths]);
731 denyReadPaths = await this._resolveFileSystemPaths(windowsFileSystemSetting.denyRead ?? []);
732 > } else if (this._os === OperatingSystem.Macintosh) { terminalSandboxEngine.ts ×19
733 allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(macFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath);
734 allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(macFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
735 denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(macFileSystemSetting.denyRead ?? []), ...(configFilePath ? [configFilePath] : [])]));
736 denyWritePaths = macFileSystemSetting.denyWrite ? await this._resolveFileSystemPaths(macFileSystemSetting.denyWrite) : undefined;
737 > } else if (this._os === OperatingSystem.Linux) { terminalSandboxEngine.ts ×19
738 > allowWritePaths = (await this._resolveFileSystemPaths(await this._updateAllowWritePathsWithWorkspaceFolders(linuxFileSystemSetting.allowWrite, commandRuntimeAllowWritePaths))).filter(path => path !== configFilePath);
739 > allowReadPaths = await this._resolveFileSystemPaths(await this._updateAllowReadPathsWithAllowWrite(linuxFileSystemSetting.allowRead, allowWritePaths, commandRuntimeAllowReadPaths));
740 > denyReadPaths = await this._resolveFileSystemPaths(this._updateDenyReadPathsWithHome([...(linuxFileSystemSetting.denyRead ?? []), ...(configFilePath ? [configFilePath] : [])]));
741 > denyWritePaths = await this._resolveFileSystemPaths(linuxFileSystemSetting.denyWrite);
742 > }
743 >
744 > return { allowReadPaths, allowWritePaths, denyReadPaths, denyWritePaths };
745 > }
747 > private async _hasFileSystemAccess(permission: TerminalSandboxFileAccessPermission, path: string, accessPaths: ITerminalSandboxFileSystemAccessPaths): Promise<boolean> {
748 > const resolvedPaths = await this._resolveFileSystemPath(path); terminalSandboxEngine.ts ×19
749 > if (permission === 'write') {
750 > if (this._os === OperatingSystem.Windows && this._matchesAnyFileSystemPath(resolvedPaths, accessPaths.denyReadPaths)) { terminalSandboxEngine.ts ×3
751 return false;
752 }
753 > if (this._matchesAnyFileSystemPath(resolvedPaths, accessPaths.denyWritePaths ?? [])) { terminalSandboxEngine.ts ×3
754 > return false; terminalSandboxEngine.ts ×2
755 > }
756 > return this._matchesAnyFileSystemPath(resolvedPaths, accessPaths.allowWritePaths); terminalSandboxEngine.ts ×3
757 > }
759 > if (this._matchesAnyFileSystemPath(resolvedPaths, [...accessPaths.allowReadPaths, ...accessPaths.allowWritePaths])) {
760 > return true;
761 > }
762 > return !this._matchesAnyFileSystemPath(resolvedPaths, accessPaths.denyReadPaths);
765 > private _matchesAnyFileSystemPath(paths: readonly string[], matchers: readonly string[]): boolean {
766 > return paths.some(path => matchers.some(matcher => this._matchesFileSystemPath(path, matcher))); terminalSandboxEngine.ts ×19
767 > }
769 > /**
770 > * Returns whether a candidate filesystem path is covered by a sandbox allow/deny
771 > * matcher. Both values are normalized with the target sandbox OS semantics before
772 > * comparison. Non-glob matchers are treated as exact-or-parent matches; glob
773 > * matchers are evaluated with VS Code's glob matcher.
774 > *
775 > * Examples:
776 > * - Linux/macOS: `/workspace/project/src/file.ts` matches `/workspace/project`.
777 > * - Linux/macOS: `/workspace/project2/file.ts` does not match `/workspace/project`.
778 > * - Windows: `C:\Repo\src\file.ts` matches `c:/repo` because matching is
779 > * case-insensitive and backslashes are normalized to `/`.
780 > * - Glob: `/workspace/project/package.json` matches `/workspace/project/*.json`.
781 > */
782 > private _matchesFileSystemPath(path: string, matcher: string): boolean {
783 > const normalizedPath = this._normalizeFileSystemAccessPath(path); terminalSandboxEngine.ts ×19
784 > const normalizedMatcher = this._normalizeFileSystemAccessPath(matcher, true);
785 > const ignoreCase = this._os === OperatingSystem.Windows;
786 > if (this._containsGlobPattern(normalizedMatcher)) {
787 > return globMatch(normalizedMatcher, normalizedPath, { ignoreCase }); terminalSandboxEngine.ts ×2
788 > }
789 > return this._fileSystemPathExtUri.isEqualOrParent(this._toFileSystemAccessUri(normalizedPath), this._toFileSystemAccessUri(normalizedMatcher)); terminalSandboxEngine.ts ×19
790 > }
792 > /**
793 > * Converts a normalized sandbox filesystem path into a pseudo URI so the common
794 > * `ExtUri.isEqualOrParent` comparer can be used instead of deprecated string
795 > * path helpers. A non-`file` scheme is intentional: it keeps comparison on the
796 > * URI path component and avoids converting through the host OS' native `fsPath`
797 > * rules, which may differ from the sandbox target OS.
798 > *
799 > * Examples:
800 > * - `/workspace/project` becomes `terminal-sandbox-path:/workspace/project`.
801 > * - `C:/Repo` becomes `terminal-sandbox-path:/C:/Repo` so Windows drive paths
802 > * are still valid URI paths for comparison.
803 > */
804 > private _toFileSystemAccessUri(path: string): URI {
805 > return URI.from({ scheme: 'terminal-sandbox-path', path: path.startsWith('/') ? path : `/${path}` }); terminalSandboxEngine.ts ×19
806 > }
808 > /**
809 > * Normalizes a path or matcher into the form used for sandbox access checks.
810 > * On Windows, backslashes are converted to `/` and URI-shaped drive paths like
811 > * `/C:/Users/me` are converted to `C:/Users/me`. Unless `preserveGlob` is true
812 > * for a glob matcher, the path is POSIX-normalized to remove redundant `.`/`..`
813 > * segments. Trailing slashes are removed except for filesystem roots.
814 > *
815 > * Examples:
816 > * - Linux/macOS: `/workspace/../workspace/app/` becomes `/workspace/app`.
817 > * - Windows: `C:\Users\me\project\` becomes `C:/Users/me/project`.
818 > * - Windows: `/C:/Users/me/project` becomes `C:/Users/me/project`.
819 > * - Glob with `preserveGlob=true`: `/workspace/project/*.json` keeps the glob
820 > * pattern intact for `globMatch`.
821 > */
822 > private _normalizeFileSystemAccessPath(path: string, preserveGlob: boolean = false): string {
823 > let normalizedPath = this._os === OperatingSystem.Windows ? path.replace(/\\/g, '/') : path; terminalSandboxEngine.ts ×19
824 > if (this._os === OperatingSystem.Windows && /^\/[a-zA-Z]:($|\/)/.test(normalizedPath)) {
825 normalizedPath = normalizedPath.slice(1);
826 }
827 > if (!preserveGlob || !this._containsGlobPattern(normalizedPath)) { terminalSandboxEngine.ts ×19
828 > normalizedPath = posix.normalize(normalizedPath);
829 > }
830 > if (normalizedPath.length > 1 && normalizedPath.endsWith('/') && !/^[a-zA-Z]:\/$/.test(normalizedPath)) {
831 normalizedPath = normalizedPath.replace(/\/+$/, '');
832 }
833 > return normalizedPath; terminalSandboxEngine.ts ×19
834 > }
836 > private _containsGlobPattern(path: string): boolean {
837 > return /[*?{\[]/.test(path); terminalSandboxEngine.ts ×19
838 > }
840 > private readonly _buildSandboxPayload = (commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment): Promise<IWindowsMxcConfig | undefined> => {
841 > return this._host.buildWindowsMxcSandboxPayload(commandLine, policy, workingDirectory, containerName, containment); terminalSandboxEngine.ts ×1
842 > };
844 > private _getCommandRuntimeFileSystemPaths(runtimeSetting: Record<string, unknown>, key: 'allowRead' | 'allowWrite'): string[] {
845 > const filesystem = runtimeSetting.filesystem; terminalSandboxEngine.ts ×11
846 > if (!this._isObjectForSandboxConfigMerge(filesystem)) {
847 > return []; terminalSandboxEngine.ts ×1
848 > }
850 > const paths = filesystem[key];
851 > if (!Array.isArray(paths)) {
852 return [];
853 }
855 > return paths.filter((path): path is string => typeof path === 'string');
858 > private _mergeAdditionalSandboxConfigProperties(target: Record<string, unknown>, additional: Record<string, unknown>): void {
859 > for (const [key, value] of Object.entries(additional)) { terminalSandboxEngine.ts ×8
860 > if (!Object.prototype.hasOwnProperty.call(target, key)) { terminalSandboxEngine.ts ×3
861 > target[key] = value;
862 > continue;
863 > }
865 > const existingValue = target[key];
866 > if (this._isObjectForSandboxConfigMerge(existingValue) && this._isObjectForSandboxConfigMerge(value)) { terminalSandboxEngine.ts ×3
867 > this._mergeAdditionalSandboxConfigProperties(existingValue, value); terminalSandboxEngine.ts ×2
868 > }
872 > private _isObjectForSandboxConfigMerge(value: unknown): value is Record<string, unknown> {
873 > return typeof value === 'object' && value !== null && !Array.isArray(value); terminalSandboxEngine.ts ×11
874 > }
876 > private async _getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy> {
877 > if (!this._windowsMxcFilesystemPolicy) { terminalSandboxMxcRuntime.ts ×11
878 > this._windowsMxcFilesystemPolicy = await this._host.getWindowsMxcFilesystemPolicy() ?? { readonlyPaths: [], readwritePaths: [] };
879 > }
880 > return this._windowsMxcFilesystemPolicy;
881 > }
883 > private async _getWindowsMxcEnvironment(): Promise<string[]> {
884 > if (!this._windowsMxcEnvironment) { terminalSandboxMxcRuntime.ts ×11
885 > this._windowsMxcEnvironment = await this._host.getWindowsMxcEnvironment() ?? [];
886 > }
887 > return this._windowsMxcEnvironment;
888 > }
890 > private _pathJoin = (...segments: string[]) => {
891 > const path = this._os === OperatingSystem.Windows ? win32 : posix; terminalSandboxEngine.ts ×1
892 > return path.join(...segments);
893 > };
895 > private _pathDirname(path: string): string {
896 > return (this._os === OperatingSystem.Windows ? win32 : posix).dirname(path); terminalSandboxEngine.ts ×10
897 > }
899 > private _getUriPath(uri: URI): string {
900 > return this._os === OperatingSystem.Windows ? this._windowsMxcRuntime.toWindowsPath(uri) : uri.path; terminalSandboxEngine.ts ×11
901 > }
903 > private async _initTempDir(): Promise<void> {
904 > if (!(await this.isEnabled())) { terminalSandboxEngine.ts ×11
905 return;
906 }
907 > this._needsForceUpdateConfigFile = true; terminalSandboxEngine.ts ×11
908 > this._tempDir = await this._host.getSandboxTempDir();
909 > if (this._tempDir) {
910 > await this._fileService.createFolder(this._tempDir);
911 > this._defaultWritePaths.push(this._getUriPath(this._tempDir));
912 > } else {
913 this._logService.warn('TerminalSandboxEngine: Cannot create sandbox settings file because no tmpDir is available in this environment');
914 }
917 > private async _updateAllowWritePathsWithWorkspaceFolders(configuredAllowWrite: string[] | undefined, commandRuntimeAllowWrite: string[] = []): Promise<string[]> {
918 > const writeRootPaths = this._host.getWriteRoots().map(folder => this._getUriPath(folder)); terminalSandboxEngine.ts ×11
919 > return [...new Set([...writeRootPaths, ...this._defaultWritePaths, ...await this._getWorkspaceStorageReadPaths(), ...(configuredAllowWrite ?? []), ...commandRuntimeAllowWrite])];
920 > }
922 > private _updateDenyReadPathsWithHome(configuredDenyRead: string[] | undefined): string[] {
923 > // TODO: On Windows, deny read on home directory. terminalSandboxEngine.ts ×10
924 > if (this._os === OperatingSystem.Windows) {
925 return [...new Set(configuredDenyRead ?? [])];
926 }
927 > const userHome = this._userHome ? this._getUriPath(this._userHome) : undefined; terminalSandboxEngine.ts ×10
928 > return [...new Set([...(configuredDenyRead ?? []), ...(userHome ? [userHome] : [])])];
929 > }
931 > private async _updateAllowReadPathsWithAllowWrite(configuredAllowRead: string[] | undefined, allowWrite: string[], commandRuntimeAllowRead: string[] = []): Promise<string[]> {
932 > return [...new Set([...(configuredAllowRead ?? []), ...getTerminalSandboxReadAllowListForCommands(this._os, this._commandAllowListKeywords, this._commandAllowListCommandDetails), ...commandRuntimeAllowRead, ...this._getSandboxRuntimeReadPaths(), ...await this._getWorkspaceStorageReadPaths(), ...allowWrite])]; terminalSandboxEngine.ts ×10
933 > }
935 > private async _resolveFileSystemPaths(paths: string[] | undefined): Promise<string[]> {
936 > const resolvedPaths = await Promise.all((paths ?? []).map(path => this._resolveFileSystemPath(path))); terminalSandboxEngine.ts ×8
937 > const seenPaths = new Set<string>();
938 > return resolvedPaths.flat().filter(path => {
939 > const comparisonKey = this._getFileSystemPathComparisonKey(path);
940 > if (seenPaths.has(comparisonKey)) {
941 > return false; terminalSandboxEngine.ts ×1
942 > }
943 > seenPaths.add(comparisonKey); terminalSandboxEngine.ts ×8
944 > return true;
945 > });
946 > }
948 > private _getFileSystemPathComparisonKey(path: string): string {
949 > return this._os === OperatingSystem.Windows ? path.replace(/\//g, '\\').toLowerCase() : path; terminalSandboxEngine.ts ×8
950 > }
952 > private async _resolveFileSystemPath(path: string): Promise<string[]> {
953 > const expandedPath = this._os === OperatingSystem.Linux ? this._expandHomePath(path) : path; terminalSandboxEngine.ts ×8
954 > if (!this._isAbsoluteFileSystemPath(expandedPath)) {
955 return [expandedPath];
956 }
958 > try {
959 > const realpath = await this._fileService.realpath(this._toFileSystemResource(expandedPath));
960 > const resolvedPath = realpath ? this._getUriPath(realpath) : undefined;
961 > // Keep the expanded path (the configured path after home expansion) so permissions apply when accessed through the symlink.
962 > // Also include the resolved path (the canonical symlink target) so the same permissions apply when accessed directly.
963 > return resolvedPath && resolvedPath !== expandedPath ? [expandedPath, resolvedPath] : [expandedPath];
964 > } catch {
965 return [expandedPath];
966 }
969 > private _isAbsoluteFileSystemPath(path: string): boolean {
970 > return (this._os === OperatingSystem.Windows ? win32 : posix).isAbsolute(path); terminalSandboxEngine.ts ×1
971 > }
973 > private _toFileSystemResource(path: string): URI {
974 > if (this._os === OperatingSystem.Windows) { terminalSandboxEngine.ts ×8
975 > return this._toWindowsFileSystemResource(path); terminalSandboxEngine.ts ×4
976 > }
977 > return this._userHome?.with({ path }) ?? this._tempDir?.with({ path }) ?? this._host.getWriteRoots()[0]?.with({ path }) ?? URI.file(path); terminalSandboxEngine.ts ×8
978 > }
980 > private _toWindowsFileSystemResource(path: string): URI {
981 > // Normalize Windows separators for URI parsing, e.g. `C:\Users\me` becomes `C:/Users/me`. terminalSandboxEngine.ts ×4
982 > const normalizedPath = path.replace(/\\/g, '/');
983 > // Match UNC paths, e.g. `//server/share/folder` becomes `file://server/share/folder`.
984 > if (/^\/\/[^/]/.test(normalizedPath)) {
985 const firstPathSeparator = normalizedPath.indexOf('/', 2);
986 if (firstPathSeparator === -1) {
987 return URI.from({ scheme: 'file', authority: normalizedPath.slice(2), path: '/' });
988 }
989 return URI.from({ scheme: 'file', authority: normalizedPath.slice(2, firstPathSeparator), path: normalizedPath.slice(firstPathSeparator) || '/' });
990 }
991 > // Match drive-letter paths, e.g. `C:/Users/me` becomes `file:///c:/Users/me`. terminalSandboxEngine.ts ×4
992 > if (/^[a-zA-Z]:($|\/)/.test(normalizedPath)) {
993 > return URI.from({ scheme: 'file', path: `/${normalizedPath[0].toLowerCase()}${normalizedPath.slice(1)}` });
994 > }
995 // Match URI-shaped drive paths, e.g. `/C:/Users/me` becomes `file:///c:/Users/me`.
996 if (/^\/[a-zA-Z]:($|\/)/.test(normalizedPath)) {
997 return URI.from({ scheme: 'file', path: `/${normalizedPath[1].toLowerCase()}${normalizedPath.slice(2)}` });
998 }
999 return URI.from({ scheme: 'file', path: normalizedPath });
1002 > private _expandHomePath(path: string): string {
1003 > const userHome = this._userHome?.path; terminalSandboxEngine.ts ×4
1004 > if (!userHome) {
1005 return path;
1006 }
1007 > if (path === '~') { terminalSandboxEngine.ts ×4
1008 return userHome;
1009 }
1010 > if (path.startsWith('~/')) { terminalSandboxEngine.ts ×4
1011 > return this._pathJoin(userHome, path.slice(2)); terminalSandboxEngine.ts ×1
1012 > }
1013 > return path; terminalSandboxEngine.ts ×4
1014 > }
1016 > private _getSandboxRuntimeReadPaths(): string[] {
1017 > if (!this._appRoot) { terminalSandboxEngine.ts ×10
1018 return [];
1019 }
1020 > if (this._os === OperatingSystem.Windows) { terminalSandboxEngine.ts ×10
1021 return this._windowsMxcRuntime.getRuntimeReadPaths(this._appRoot, this._mxcPath);
1022 }
1023 > const paths: string[] = [this._appRoot]; terminalSandboxEngine.ts ×10
1024 > if (this._execPath) {
1025 > for (const path of [this._execPath, this._pathDirname(this._execPath)]) {
1026 > if (!this._isPathUnderAppRoot(path)) {
1027 > paths.push(path); agentHostSandboxEngine.ts ×7
1028 > }
1030 > }
1031 > return paths;
1032 > }
1034 > private _isPathUnderAppRoot(path: string): boolean {
1035 > if (!this._appRoot) { terminalSandboxEngine.ts ×10
1036 return false;
1037 }
1038 > return path === this._appRoot || path.startsWith(`${this._appRoot}${this._os === OperatingSystem.Windows ? win32.sep : posix.sep}`); terminalSandboxEngine.ts ×10
1039 > }
1041 > private async _getWorkspaceStorageReadPaths(): Promise<string[]> {
1042 > const root = await this._host.getWorkspaceStorageReadRoot(); terminalSandboxEngine.ts ×11
1043 > return root ? [this._getUriPath(root)] : [];
1044 > }
1046 > private _getDefaultWindowsMxcCwd(): URI | undefined {
1047 > return this._host.getWriteRoots()[0]; terminalSandboxEngine.ts ×1
1048 > }
1050 > private _getSandboxConfiguredEnabledValue(): AgentSandboxEnabledValue {
1051 > return this._normalizeSandboxEnabledValue(this._getSettingValue<AgentSandboxEnabledSettingValue>(AgentSandboxSettingId.AgentSandboxEnabled)); terminalSandboxEngine.ts ×2
1052 > }
1054 > private _getSandboxConfiguredWindowsEnabledValue(): AgentSandboxEnabledValue {
1055 > return this._normalizeSandboxEnabledValue(this._getSettingValue<AgentSandboxEnabledSettingValue>(AgentSandboxSettingId.AgentSandboxWindowsEnabled)); terminalSandboxEngine.ts ×2
1056 > }
1058 > private _normalizeSandboxEnabledValue(value: AgentSandboxEnabledSettingValue | undefined): AgentSandboxEnabledValue {
1059 > return value === undefined ? AgentSandboxEnabledValue.Off : normalizeAgentSandboxEnabledValue(value); terminalSandboxEngine.ts ×7
1060 > }
1062 > private _isSandboxAllowNetworkConfigured(): boolean {
1063 > if (this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxAllowNetwork) === true) { terminalSandboxEngine.ts ×2
1064 > return true; terminalSandboxEngine.ts ×1
1065 > }
1066 > if (this._os === OperatingSystem.Windows) { terminalSandboxEngine.ts ×1
1067 > return this._getSandboxConfiguredWindowsEnabledValue() === AgentSandboxEnabledValue.AllowNetwork; terminalSandboxEngine.ts ×1
1068 > }
1069 > return this._getSandboxConfiguredEnabledValue() === AgentSandboxEnabledValue.AllowNetwork; terminalSandboxEngine.ts ×1
1072 > private _areUnsandboxedCommandsAllowed(): boolean {
1073 > return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxAllowUnsandboxedCommands) === true; terminalSandboxEngine.ts ×1
1074 > }
1076 > private _areRetryWithAllowNetworkRequestsAllowed(): boolean {
1077 > return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxRetryWithAllowNetworkRequests) === true; terminalSandboxEngine.ts ×10
1078 > }
1080 > private _getSettingValue<T>(settingId: AgentSandboxSettingId | AgentNetworkDomainSettingId): T | undefined {
1081 > return this._host.getSandboxSetting<T>(settingId); terminalSandboxEngine.ts ×7
1082 > }