terminalSandboxEngine.ts ×71

Frontier kind: Code frontier

unlabeled · c_23b13d05c400

489 tests · 11917 LOC · 56 files · introduces 0 tests · 746 LOC · 6 files

Introduces — evidence that enters the hierarchy at this concept

Code
107 ranges746 lines · 6 files
Tests
0 tests

Contains — complete concept membership

All code (extent)
1741 ranges11917 lines · 56 files · Browse complete extent
All tests (intent)
489 testsBrowse complete intent

Neighbourhood graph

The orange circle is the focus. Violet and green circles are every ancestor and descendant, broader and narrower, at any distance; blue squares and pink diamonds are the introduced files and exact introduced tests of every visible concept, not only the focus's. Arrows point from broader to narrower concepts and bridge only concepts omitted from this view. Undirected links show source or test introduction. Concept and file size follows LOC; exact test nodes use test-count units.

Introduced files, introduced tests, and structurally relevant concept specialization

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 native relationship evidence on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the native relationship evidence on this page while the interactive map is unavailable.

Native relationship evidence

Every exact file and test below is linked only from the concept that introduces it.

Introduced tests

Every collected test enters the hierarchy at exactly one concept.

No tests are introduced at this concept. Its intent tests are introduced by other concepts.

Introduced code

Every collected source range enters the hierarchy at exactly one concept.

6 files ranked by introduced lines: 746 introduced LOC across 107 ranges. Expand a file to inspect source; the > gutter marks introduced lines.

src/vs/platform/sandbox/common/terminalSandboxEngine.ts 343 introduced LOC · 71 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxEngine.ts
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,
159 @IFileService private readonly _fileService: IFileService,
167 this._register(this._host.onDidChangeRoots(() => this.setNeedsForceUpdateConfigFile()));
168 }
170 > async isEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
171 return this._isSandboxConfiguredEnabled(precheckInputs);
172 }
174 > async isSandboxAllowNetworkEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
175 if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) {
176 return false;
178 return this._isSandboxAllowNetworkConfigured();
179 }
181 > areUnsandboxedCommandsAllowed(): boolean {
182 return this._areUnsandboxedCommandsAllowed();
183 }
185 > areRetryWithAllowNetworkRequestsAllowed(): boolean {
186 return this._areRetryWithAllowNetworkRequestsAllowed();
187 }
189 > async getOS(): Promise<OperatingSystem> {
190 this._os = await this._host.getOS();
191 return this._os;
192 }
194 > getTempDir(): URI | undefined {
195 return this._tempDir;
196 }
198 > setNeedsForceUpdateConfigFile(): void {
199 this._needsForceUpdateConfigFile = true;
200 }
202 > getResolvedNetworkDomains(): ITerminalSandboxResolvedNetworkDomains {
203 const allowedDomains = this._getSettingValue<string[]>(AgentNetworkDomainSettingId.AllowedNetworkDomains) ?? [];
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();
210 const retryWithAllowNetworkRequests = this._areRetryWithAllowNetworkRequestsAllowed();
311 };
312 }
314 > async checkForSandboxingPrereqs(forceRefresh: boolean = false, precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxPrerequisiteCheckResult> {
315 if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) {
316 return {
356 };
357 }
359 > async checkFileAccess(permission: TerminalSandboxFileAccessPermission, paths: readonly string[], precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxFileAccessCheckResult> {
360 if (!(await this._isSandboxConfiguredEnabled(precheckInputs))) {
361 return { allowed: true, denied: [] };
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))) {
383 return undefined;
390 return this._sandboxConfigPath;
391 }
393 > async getMissingSandboxDependencies(): Promise<string[]> {
394 const os = await this.getOS();
395 if (os === OperatingSystem.Windows) {
410 return missing;
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) {
421 return;
427 }
428 }
430 > // ---- private helpers ----------------------------------------------------
431 >
432 > private async _checkSandboxDependencies(forceRefresh = false): Promise<boolean> {
433 const os = await this.getOS();
434 if (os === OperatingSystem.Windows) {
454 return status ? status.bubblewrapInstalled && status.bubblewrapUsable && status.socatInstalled : true;
455 }
457 > private _getBubblewrapRemediations(): readonly TerminalSandboxPreCheckRemediation[] | undefined {
458 return [TerminalSandboxPreCheckRemediation.DisableUnprivilagedusernamespaceRestriction];
459 }
461 > private _quoteShellArgument(value: string): string {
462 return `'${value.replace(/'/g, `'\\''`)}'`;
463 }
465 > private _getSandboxCommandWithPreservedCwd(command: string, cwd: URI | undefined): string {
466 if (this._os !== OperatingSystem.Linux || !cwd?.path || cwd.path === this._tempDir?.path) {
467 return command;
469 return `cd ${this._quoteShellArgument(cwd.path)} && ${command}`;
470 }
472 > private _wrapSandboxRuntimeCommandForLaunch(sandboxRuntimeCommand: string, cwd: URI | undefined): string {
473 const tempDirPath = this._tempDir?.path;
474 return this._os === OperatingSystem.Linux && cwd?.path && tempDirPath && cwd.path !== tempDirPath
476 : sandboxRuntimeCommand;
477 }
479 > private _wrapUnsandboxedCommand(command: string, shell?: string): string {
480 if (this._os === OperatingSystem.Windows) {
481 return this._windowsMxcRuntime.wrapUnsandboxedCommand(command);
489 return `env TMPDIR="${this._tempDir.path}" ${this._quoteShellArgument(shell)} -c ${this._quoteShellArgument(command)}`;
490 }
492 > private _getBlockedDomains(command: string): { blockedDomains: string[]; deniedDomains: string[] } {
493 if (this._isSandboxAllowNetworkConfigured()) {
494 return { blockedDomains: [], deniedDomains: [] };
518 };
519 }
521 > private _extractDomains(command: string): string[] {
522 const domains = new Set<string>();
523 let match: RegExpExecArray | null;
549 return [...domains];
550 }
552 > private _extractDomainFromUrl(value: string): string | undefined {
553 try {
554 const authority = URI.parse(value).authority;
558 }
559 }
561 > private _normalizeCommandKeywords(commandKeywords: readonly string[]): string[] {
562 return [...new Set(commandKeywords.map(keyword => keyword.toLowerCase()))].sort();
563 }
565 > private _normalizeCommandDetails(commandDetails: readonly ITerminalSandboxCommand[]): ITerminalSandboxCommand[] {
566 const seen = new Set<string>();
567 const result: ITerminalSandboxCommand[] = [];
576 return result.sort((a, b) => a.keyword.localeCompare(b.keyword) || a.args.join('\0').localeCompare(b.args.join('\0')));
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;
589 }
591 > private async _isSandboxConfiguredEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean> {
592 if (!this._isSandboxAllowedByPrecheckInputs(precheckInputs)) {
593 return false;
601 return isAgentSandboxEnabledValue(value);
602 }
604 > private async _resolveRuntimeInfo(): Promise<void> {
605 if (this._runtimeResolved) {
606 return;
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) {
624 await this._initTempDir();
704 return this._sandboxConfigPath;
705 }
707 > private async _getFileSystemAccessPaths(configFilePath: string | undefined): Promise<ITerminalSandboxFileSystemAccessPaths> {
708 const linuxFileSystemSetting = this._os === OperatingSystem.Linux
709 ? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxLinuxFileSystem) ?? {}
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);
749 if (permission === 'write') {
762 return !this._matchesAnyFileSystemPath(resolvedPaths, accessPaths.denyReadPaths);
763 }
765 > private _matchesAnyFileSystemPath(paths: readonly string[], matchers: readonly string[]): boolean {
766 return paths.some(path => matchers.some(matcher => this._matchesFileSystemPath(path, matcher)));
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);
784 const normalizedMatcher = this._normalizeFileSystemAccessPath(matcher, true);
789 return this._fileSystemPathExtUri.isEqualOrParent(this._toFileSystemAccessUri(normalizedPath), this._toFileSystemAccessUri(normalizedMatcher));
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}` });
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;
824 if (this._os === OperatingSystem.Windows && /^\/[a-zA-Z]:($|\/)/.test(normalizedPath)) {
833 return normalizedPath;
834 }
836 > private _containsGlobPattern(path: string): boolean {
837 return /[*?{\[]/.test(path);
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);
842 };
844 > private _getCommandRuntimeFileSystemPaths(runtimeSetting: Record<string, unknown>, key: 'allowRead' | 'allowWrite'): string[] {
845 const filesystem = runtimeSetting.filesystem;
846 if (!this._isObjectForSandboxConfigMerge(filesystem)) {
855 return paths.filter((path): path is string => typeof path === 'string');
856 }
858 > private _mergeAdditionalSandboxConfigProperties(target: Record<string, unknown>, additional: Record<string, unknown>): void {
859 for (const [key, value] of Object.entries(additional)) {
860 if (!Object.prototype.hasOwnProperty.call(target, key)) {
869 }
870 }
872 > private _isObjectForSandboxConfigMerge(value: unknown): value is Record<string, unknown> {
873 return typeof value === 'object' && value !== null && !Array.isArray(value);
874 }
876 > private async _getWindowsMxcFilesystemPolicy(): Promise<IWindowsMxcFilesystemPolicy> {
877 if (!this._windowsMxcFilesystemPolicy) {
878 this._windowsMxcFilesystemPolicy = await this._host.getWindowsMxcFilesystemPolicy() ?? { readonlyPaths: [], readwritePaths: [] };
880 return this._windowsMxcFilesystemPolicy;
881 }
883 > private async _getWindowsMxcEnvironment(): Promise<string[]> {
884 if (!this._windowsMxcEnvironment) {
885 this._windowsMxcEnvironment = await this._host.getWindowsMxcEnvironment() ?? [];
887 return this._windowsMxcEnvironment;
888 }
890 > private _pathJoin = (...segments: string[]) => {
891 const path = this._os === OperatingSystem.Windows ? win32 : posix;
892 return path.join(...segments);
893 };
895 > private _pathDirname(path: string): string {
896 return (this._os === OperatingSystem.Windows ? win32 : posix).dirname(path);
897 }
899 > private _getUriPath(uri: URI): string {
900 return this._os === OperatingSystem.Windows ? this._windowsMxcRuntime.toWindowsPath(uri) : uri.path;
901 }
903 > private async _initTempDir(): Promise<void> {
904 if (!(await this.isEnabled())) {
905 return;
914 }
915 }
917 > private async _updateAllowWritePathsWithWorkspaceFolders(configuredAllowWrite: string[] | undefined, commandRuntimeAllowWrite: string[] = []): Promise<string[]> {
918 const writeRootPaths = this._host.getWriteRoots().map(folder => this._getUriPath(folder));
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.
924 if (this._os === OperatingSystem.Windows) {
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])];
933 }
935 > private async _resolveFileSystemPaths(paths: string[] | undefined): Promise<string[]> {
936 const resolvedPaths = await Promise.all((paths ?? []).map(path => this._resolveFileSystemPath(path)));
937 const seenPaths = new Set<string>();
945 });
946 }
948 > private _getFileSystemPathComparisonKey(path: string): string {
949 return this._os === OperatingSystem.Windows ? path.replace(/\//g, '\\').toLowerCase() : path;
950 }
952 > private async _resolveFileSystemPath(path: string): Promise<string[]> {
953 const expandedPath = this._os === OperatingSystem.Linux ? this._expandHomePath(path) : path;
954 if (!this._isAbsoluteFileSystemPath(expandedPath)) {
966 }
967 }
969 > private _isAbsoluteFileSystemPath(path: string): boolean {
970 return (this._os === OperatingSystem.Windows ? win32 : posix).isAbsolute(path);
971 }
973 > private _toFileSystemResource(path: string): URI {
974 if (this._os === OperatingSystem.Windows) {
975 return this._toWindowsFileSystemResource(path);
977 return this._userHome?.with({ path }) ?? this._tempDir?.with({ path }) ?? this._host.getWriteRoots()[0]?.with({ path }) ?? URI.file(path);
978 }
980 > private _toWindowsFileSystemResource(path: string): URI {
981 // Normalize Windows separators for URI parsing, e.g. `C:\Users\me` becomes `C:/Users/me`.
982 const normalizedPath = path.replace(/\\/g, '/');
999 return URI.from({ scheme: 'file', path: normalizedPath });
1000 }
1002 > private _expandHomePath(path: string): string {
1003 const userHome = this._userHome?.path;
1004 if (!userHome) {
1013 return path;
1014 }
1016 > private _getSandboxRuntimeReadPaths(): string[] {
1017 if (!this._appRoot) {
1018 return [];
1031 return paths;
1032 }
1034 > private _isPathUnderAppRoot(path: string): boolean {
1035 if (!this._appRoot) {
1036 return false;
1038 return path === this._appRoot || path.startsWith(`${this._appRoot}${this._os === OperatingSystem.Windows ? win32.sep : posix.sep}`);
1039 }
1041 > private async _getWorkspaceStorageReadPaths(): Promise<string[]> {
1042 const root = await this._host.getWorkspaceStorageReadRoot();
1043 return root ? [this._getUriPath(root)] : [];
1044 }
1046 > private _getDefaultWindowsMxcCwd(): URI | undefined {
1047 return this._host.getWriteRoots()[0];
1048 }
1050 > private _getSandboxConfiguredEnabledValue(): AgentSandboxEnabledValue {
1051 return this._normalizeSandboxEnabledValue(this._getSettingValue<AgentSandboxEnabledSettingValue>(AgentSandboxSettingId.AgentSandboxEnabled));
1052 }
1054 > private _getSandboxConfiguredWindowsEnabledValue(): AgentSandboxEnabledValue {
1055 return this._normalizeSandboxEnabledValue(this._getSettingValue<AgentSandboxEnabledSettingValue>(AgentSandboxSettingId.AgentSandboxWindowsEnabled));
1056 }
1058 > private _normalizeSandboxEnabledValue(value: AgentSandboxEnabledSettingValue | undefined): AgentSandboxEnabledValue {
1059 return value === undefined ? AgentSandboxEnabledValue.Off : normalizeAgentSandboxEnabledValue(value);
1060 }
1062 > private _isSandboxAllowNetworkConfigured(): boolean {
1063 if (this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxAllowNetwork) === true) {
1064 return true;
1069 return this._getSandboxConfiguredEnabledValue() === AgentSandboxEnabledValue.AllowNetwork;
1070 }
1072 > private _areUnsandboxedCommandsAllowed(): boolean {
1073 return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxAllowUnsandboxedCommands) === true;
1074 }
1076 > private _areRetryWithAllowNetworkRequestsAllowed(): boolean {
1077 return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxRetryWithAllowNetworkRequests) === true;
1078 }
1080 > private _getSettingValue<T>(settingId: AgentSandboxSettingId | AgentNetworkDomainSettingId): T | undefined {
1081 return this._host.getSandboxSetting<T>(settingId);
1082 }
src/vs/platform/sandbox/common/terminalSandboxService.ts 159 introduced LOC · 14 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxService.ts
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 { CancellationToken } from '../../../base/common/cancellation.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { URI } from '../../../base/common/uri.js';
9 > import { OperatingSystem, OS } from '../../../base/common/platform.js';
10 > import { createDecorator } from '../../instantiation/common/instantiation.js';
11 > import { TerminalCapability } from '../../terminal/common/capabilities/capabilities.js';
12 >
13 > export const ITerminalSandboxService = createDecorator<ITerminalSandboxService>('terminalSandboxService');
14 >
15 > export interface ITerminalSandboxResolvedNetworkDomains {
16 > allowedDomains: string[];
17 > deniedDomains: string[];
18 > }
19 >
20 > export const enum TerminalSandboxPrerequisiteCheck {
21 > Config = 'config',
22 > Dependencies = 'dependencies',
23 > Bubblewrap = 'bubblewrap',
24 > }
25 >
26 > export const enum TerminalSandboxPreCheckRemediation {
27 > DisableUnprivilagedusernamespaceRestriction = 'disableUserNamespaceRestriction',
28 > }
29 >
30 > export interface ITerminalSandboxPrerequisiteCheckResult {
31 > enabled: boolean;
32 > sandboxConfigPath: string | undefined;
33 > failedCheck: TerminalSandboxPrerequisiteCheck | undefined;
34 > missingDependencies?: string[];
35 > canInstallMissingDependencies?: boolean;
36 > remediations?: readonly TerminalSandboxPreCheckRemediation[];
37 > detail?: string;
38 > }
39 >
40 > export interface ITerminalSandboxWrapResult {
41 > command: string;
42 > isSandboxWrapped: boolean;
43 > blockedDomains?: string[];
44 > deniedDomains?: string[];
45 > requiresUnsandboxConfirmation?: boolean;
46 > requiresAllowNetworkConfirmation?: boolean;
47 > }
48 >
49 > export type TerminalSandboxFileAccessPermission = 'read' | 'write';
50 >
51 > export interface ITerminalSandboxFileAccessCheckResult {
52 > allowed: boolean;
53 > denied: string[];
54 > }
55 >
56 > export interface ITerminalSandboxPrecheckInputs {
57 > /**
58 > * Whether the current caller is using the default approval permission flow.
59 > */
60 > readonly isDefaultApprovalPermissionEnabled?: boolean;
61 > }
62 >
63 > export interface ITerminalSandboxCommand {
64 > /**
65 > * Normalized command name without path or executable suffix.
66 > * For example, `/usr/bin/git` and `git.exe` both normalize to `git`.
67 > */
68 > keyword: string;
69 > /**
70 > * Command arguments after the executable token. These are used for
71 > * argument-sensitive sandbox allow-list rules, such as matching a specific
72 > * subcommand while ignoring global options.
73 > */
74 > args: readonly string[];
75 > }
76 >
77 > /**
78 > * Abstraction over terminal operations needed by the install flow.
79 > * Provided by the browser-layer caller so the common-layer service
80 > * does not import browser types directly.
81 > */
82 > export interface ISandboxDependencyInstallTerminal {
83 > sendText(text: string, addNewLine?: boolean): Promise<void>;
84 > focus(): void;
85 > capabilities: {
86 > get(id: TerminalCapability.CommandDetection): { onCommandFinished: Event<{ exitCode: number | undefined }> } | undefined;
87 > onDidAddCapability: Event<{ id: TerminalCapability }>;
88 > };
89 > onDidInputData: Event<string>;
90 > onDisposed: Event<unknown>;
91 > }
92 >
93 > export interface ISandboxDependencyInstallOptions {
94 > /**
95 > * Creates or obtains a terminal for running the install command.
96 > */
97 > createTerminal(): Promise<ISandboxDependencyInstallTerminal>;
98 > /**
99 > * Focuses the terminal for password entry.
100 > */
101 > focusTerminal(terminal: ISandboxDependencyInstallTerminal): Promise<void>;
102 > }
103 >
104 > export interface ISandboxDependencyInstallResult {
105 > exitCode: number | undefined;
106 > }
107 >
108 > export interface ITerminalSandboxService {
109 > readonly _serviceBrand: undefined;
110 > isEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean>;
111 > isSandboxAllowNetworkEnabled(precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<boolean>;
112 > getOS(): Promise<OperatingSystem>;
113 > checkForSandboxingPrereqs(forceRefresh?: boolean, precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxPrerequisiteCheckResult>;
114 > /**
115 > * Wraps a command line for sandbox execution. Command details are optional,
116 > * but when provided they are used to derive command-specific read/write
117 > * allow-list entries. When explicitly requested, `requestAllowNetwork`
118 > * retains sandbox execution while using a network-unrestricted config.
119 > */
120 > wrapCommand(command: string, requestUnsandboxedExecution?: boolean, shell?: string, cwd?: URI, commandDetails?: readonly ITerminalSandboxCommand[], requestAllowNetwork?: boolean): Promise<ITerminalSandboxWrapResult>;
121 > checkFileAccess(permission: TerminalSandboxFileAccessPermission, paths: readonly string[], precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<ITerminalSandboxFileAccessCheckResult>;
122 > getSandboxConfigPath(forceRefresh?: boolean, precheckInputs?: ITerminalSandboxPrecheckInputs): Promise<string | undefined>;
123 > getTempDir(): URI | undefined;
124 > setNeedsForceUpdateConfigFile(): void;
125 > getResolvedNetworkDomains(): ITerminalSandboxResolvedNetworkDomains;
126 > getMissingSandboxDependencies(): Promise<string[]>;
127 > installMissingSandboxDependencies(missingDependencies: string[], sessionResource: URI | undefined, token: CancellationToken, options: ISandboxDependencyInstallOptions): Promise<ISandboxDependencyInstallResult>;
128 > runSandboxRemediation(remediation: TerminalSandboxPreCheckRemediation, sessionResource: URI | undefined, token: CancellationToken, options: ISandboxDependencyInstallOptions): Promise<ISandboxDependencyInstallResult>;
129 > }
130 >
131 > export class NullTerminalSandboxService implements ITerminalSandboxService {
132 > readonly _serviceBrand: undefined;
133 >
134 > async isEnabled(): Promise<boolean> {
135 return false;
136 }
138 > async isSandboxAllowNetworkEnabled(): Promise<boolean> {
139 return false;
140 }
142 > async getOS(): Promise<OperatingSystem> {
143 return OS;
144 }
146 > async checkForSandboxingPrereqs(): Promise<ITerminalSandboxPrerequisiteCheckResult> {
147 return { enabled: false, sandboxConfigPath: undefined, failedCheck: undefined };
148 }
150 > async wrapCommand(command: string): Promise<ITerminalSandboxWrapResult> {
151 return { command, isSandboxWrapped: false };
152 }
154 > async checkFileAccess(): Promise<ITerminalSandboxFileAccessCheckResult> {
155 return { allowed: true, denied: [] };
156 }
158 > async getSandboxConfigPath(): Promise<string | undefined> {
159 return undefined;
160 }
162 > getTempDir(): URI | undefined {
163 return undefined;
164 }
166 > setNeedsForceUpdateConfigFile(): void {
167 // No-op.
168 }
170 > getResolvedNetworkDomains(): ITerminalSandboxResolvedNetworkDomains {
171 return { allowedDomains: [], deniedDomains: [] };
172 }
174 > async getMissingSandboxDependencies(): Promise<string[]> {
175 return [];
176 }
178 > async installMissingSandboxDependencies(): Promise<ISandboxDependencyInstallResult> {
179 return { exitCode: undefined };
180 }
182 > async runSandboxRemediation(): Promise<ISandboxDependencyInstallResult> {
183 return { exitCode: undefined };
184 }
src/vs/platform/sandbox/common/terminalSandboxReadAllowList.ts 113 introduced LOC · 3 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxReadAllowList.ts
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 { OperatingSystem } from '../../../base/common/platform.js';
7 > import type { ITerminalSandboxCommand } from './terminalSandboxService.js';
8 > import { type ITerminalSandboxCommandRule, matchesTerminalSandboxCommandRule } from './terminalSandboxCommandRules.js';
9 >
10 > export const enum TerminalSandboxReadAllowListOperation {
11 > Git = 'git',
12 > Node = 'node',
13 > Rust = 'rust',
14 > Go = 'go',
15 > Python = 'python',
16 > Java = 'java',
17 > Dotnet = 'dotnet',
18 > Nuget = 'nuget',
19 > Msbuild = 'msbuild',
20 > Ruby = 'ruby',
21 > NativeBuild = 'nativeBuild',
22 > Conan = 'conan',
23 > GnuPG = 'gnupg',
24 > Ssh = 'ssh',
25 > }
26 >
27 > const terminalSandboxReadAllowListKeywordMap: ReadonlyMap<string, TerminalSandboxReadAllowListOperation> = new Map([
28 > ['git', TerminalSandboxReadAllowListOperation.Git],
29 > ['gh', TerminalSandboxReadAllowListOperation.Git],
30 > ['gpg', TerminalSandboxReadAllowListOperation.GnuPG],
31 > ['node', TerminalSandboxReadAllowListOperation.Node],
32 > ['npm', TerminalSandboxReadAllowListOperation.Node],
33 > ['npx', TerminalSandboxReadAllowListOperation.Node],
34 > ['pnpm', TerminalSandboxReadAllowListOperation.Node],
35 > ['yarn', TerminalSandboxReadAllowListOperation.Node],
36 > ['corepack', TerminalSandboxReadAllowListOperation.Node],
37 > ['bun', TerminalSandboxReadAllowListOperation.Node],
38 > ['deno', TerminalSandboxReadAllowListOperation.Node],
39 > ['nvm', TerminalSandboxReadAllowListOperation.Node],
40 > ['volta', TerminalSandboxReadAllowListOperation.Node],
41 > ['fnm', TerminalSandboxReadAllowListOperation.Node],
42 > ['asdf', TerminalSandboxReadAllowListOperation.Node],
43 > ['mise', TerminalSandboxReadAllowListOperation.Node],
44 > ['cargo', TerminalSandboxReadAllowListOperation.Rust],
45 > ['rustc', TerminalSandboxReadAllowListOperation.Rust],
46 > ['rustup', TerminalSandboxReadAllowListOperation.Rust],
47 > ['go', TerminalSandboxReadAllowListOperation.Go],
48 > ['gofmt', TerminalSandboxReadAllowListOperation.Go],
49 > ['python', TerminalSandboxReadAllowListOperation.Python],
50 > ['python3', TerminalSandboxReadAllowListOperation.Python],
51 > ['pip', TerminalSandboxReadAllowListOperation.Python],
52 > ['pip3', TerminalSandboxReadAllowListOperation.Python],
53 > ['poetry', TerminalSandboxReadAllowListOperation.Python],
54 > ['uv', TerminalSandboxReadAllowListOperation.Python],
55 > ['pipx', TerminalSandboxReadAllowListOperation.Python],
56 > ['pyenv', TerminalSandboxReadAllowListOperation.Python],
57 > ['java', TerminalSandboxReadAllowListOperation.Java],
58 > ['javac', TerminalSandboxReadAllowListOperation.Java],
59 > ['jar', TerminalSandboxReadAllowListOperation.Java],
60 > ['mvn', TerminalSandboxReadAllowListOperation.Java],
61 > ['mvnw', TerminalSandboxReadAllowListOperation.Java],
62 > ['gradle', TerminalSandboxReadAllowListOperation.Java],
63 > ['gradlew', TerminalSandboxReadAllowListOperation.Java],
64 > ['sdk', TerminalSandboxReadAllowListOperation.Java],
65 > ['dotnet', TerminalSandboxReadAllowListOperation.Dotnet],
66 > ['nuget', TerminalSandboxReadAllowListOperation.Nuget],
67 > ['msbuild', TerminalSandboxReadAllowListOperation.Msbuild],
68 > ['ruby', TerminalSandboxReadAllowListOperation.Ruby],
69 > ['gem', TerminalSandboxReadAllowListOperation.Ruby],
70 > ['bundle', TerminalSandboxReadAllowListOperation.Ruby],
71 > ['bundler', TerminalSandboxReadAllowListOperation.Ruby],
72 > ['rake', TerminalSandboxReadAllowListOperation.Ruby],
73 > ['rbenv', TerminalSandboxReadAllowListOperation.Ruby],
74 > ['rvm', TerminalSandboxReadAllowListOperation.Ruby],
75 > ['ccache', TerminalSandboxReadAllowListOperation.NativeBuild],
76 > ['sccache', TerminalSandboxReadAllowListOperation.NativeBuild],
77 > ['cmake', TerminalSandboxReadAllowListOperation.NativeBuild],
78 > ['conan', TerminalSandboxReadAllowListOperation.Conan],
79 > ]);
80 >
81 > /**
82 > * Paths that common developer tools typically need to read when the user's home
83 > * directory is broadly denied. Broad keyword-based rules intentionally avoid obvious
84 > * credential and key material such as ~/.ssh, ~/.gnupg, cloud credentials,
85 > * package manager auth files, and git credential stores. Sensitive operations
86 > * should only be referenced by command-detail rules scoped to commands or
87 > * subcommands that require them.
88 > */
89 >
90 function getTerminalSandboxReadAllowListForOperation(operation: TerminalSandboxReadAllowListOperation, os: OperatingSystem): readonly string[] {
91 if (os === OperatingSystem.Windows) {
338 }
339 }
341 function getTerminalSandboxReadAllowListForCommandDetails(os: OperatingSystem, commandDetails: readonly ITerminalSandboxCommand[]): readonly string[] {
342 const operations = new Set<TerminalSandboxReadAllowListOperation>();
352 return [...new Set(paths)];
353 }
355 > /**
356 > * Command-detail allow-list rules match parsed command executables.
357 > *
358 > * For example, `git rebase main` matches the `git` rule below, while
359 > * `gpg --list-keys` matches the `gpg` rule.
360 > */
361 > const terminalSandboxReadAllowListCommandDetailRules: readonly ITerminalSandboxCommandRule<TerminalSandboxReadAllowListOperation>[] = [
362 > {
363 > keywords: ['gpg', 'gpg2'],
364 > value: TerminalSandboxReadAllowListOperation.GnuPG,
365 > },
366 > {
367 > keywords: ['git'],
368 > value: TerminalSandboxReadAllowListOperation.GnuPG,
369 > },
370 > {
371 > keywords: ['git', 'ssh', 'scp', 'sftp', 'rsync'],
372 > value: TerminalSandboxReadAllowListOperation.Ssh,
373 > },
374 > ];
375 >
376 > export function getTerminalSandboxReadAllowListForCommands(os: OperatingSystem, commandKeywords: readonly string[], commandDetails: readonly ITerminalSandboxCommand[] = []): readonly string[] {
377 if (commandKeywords.length === 0) {
378 return getTerminalSandboxReadAllowListForCommandDetails(os, commandDetails);
src/vs/platform/sandbox/common/terminalSandboxMxcRuntime.ts 66 introduced LOC · 12 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxMxcRuntime.ts
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 { win32 } from '../../../base/common/path.js';
7 > import { URI } from '../../../base/common/uri.js';
8 > import { createDecorator } from '../../instantiation/common/instantiation.js';
9 > import type { IWindowsMxcConfig, IWindowsMxcPolicyContainment, IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
10 >
11 > export interface IWindowsMxcConfigOptions {
12 > command: string;
13 > shell?: string;
14 > cwd: URI | undefined;
15 > tempDir: URI;
16 > schemaVersion?: string;
17 > allowNetwork: boolean;
18 > allowReadPaths: string[];
19 > allowWritePaths: string[];
20 > denyReadPaths: string[];
21 > env: string[];
22 > }
23 >
24 > export type IWindowsMxcBuildSandboxPayload = (commandLine: string, policy: IWindowsMxcSandboxPolicy, workingDirectory?: string, containerName?: string, containment?: IWindowsMxcPolicyContainment) => Promise<IWindowsMxcConfig | undefined>;
25 >
26 > export const IWindowsMxcTerminalSandboxRuntime = createDecorator<IWindowsMxcTerminalSandboxRuntime>('windowsMxcTerminalSandboxRuntime');
27 >
28 > export interface IWindowsMxcTerminalSandboxRuntime {
29 > readonly _serviceBrand: undefined;
30 >
31 > getExecutablePath(appRoot: string, nativeModulesDir: string, arch: string | undefined): string;
32 > getRuntimeReadPaths(appRoot: string | undefined, executablePath: string | undefined): string[];
33 > createConfig(options: IWindowsMxcConfigOptions, buildSandboxPayload: IWindowsMxcBuildSandboxPayload): Promise<IWindowsMxcConfig>;
34 > wrapCommand(executablePath: string, configPath: string): string;
35 > wrapUnsandboxedCommand(command: string): string;
36 > toWindowsPath(uri: URI): string;
37 > }
38 >
39 > /**
40 > * Windows-only MXC integration for terminal sandboxing.
41 > *
42 > * This class is intentionally isolated from the SRT-backed runtime so it can be
43 > * removed once SRT supports Windows sandboxing.
44 > */
45 > export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSandboxRuntime {
46 declare readonly _serviceBrand: undefined;
47
48 private readonly _configVersion = '0.6.0-alpha';
50 > getExecutablePath(appRoot: string, nativeModulesDir: string, arch: string | undefined): string {
51 const binArch = arch === 'arm64' ? 'arm64' : 'x64';
52 return win32.join(appRoot, nativeModulesDir, '@microsoft', 'mxc-sdk', 'bin', binArch, 'wxc-exec.exe');
53 }
55 > getRuntimeReadPaths(appRoot: string | undefined, executablePath: string | undefined): string[] {
56 const paths: string[] = [];
57 if (appRoot) {
63 return [...new Set(paths)];
64 }
66 > async createConfig(options: IWindowsMxcConfigOptions, buildSandboxPayload: IWindowsMxcBuildSandboxPayload): Promise<IWindowsMxcConfig> {
67 const tempDirPath = this.toWindowsPath(options.tempDir);
68 const shell = options.shell
96 return config;
97 }
99 > wrapCommand(executablePath: string, configPath: string): string {
100 return `& ${this._quotePowerShellArgument(executablePath)} ${this._quotePowerShellArgument(configPath)}`;
101 }
103 > wrapUnsandboxedCommand(command: string): string {
104 return command;
105 }
107 > toWindowsPath(uri: URI): string {
108 let value: string;
109 if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
116 return this._normalizeWindowsPath(value);
117 }
119 > private _normalizeWindowsPath(path: string): string {
120 return path.replace(/\//g, '\\');
121 }
123 > private _createNetworkPolicy(allowNetwork: boolean): NonNullable<IWindowsMxcSandboxPolicy['network']> {
124 // MXC does not support per-host network policies on Windows. Rely on the
125 // overall allow/block policy instead of emitting unsupported host lists.
126 return { allowOutbound: allowNetwork };
127 }
129 > private _quotePowerShellArgument(value: string): string {
130 return `'${value.replace(/'/g, `''`)}'`;
131 }
133 > private _quoteWindowsCommandLineArgument(value: string): string {
134 return `"${value.replace(/(\\*)"/g, '$1$1\\"').replace(/\\+$/g, '$&$&')}"`;
135 }
src/vs/platform/sandbox/common/terminalSandboxCommandRules.ts 34 introduced LOC · 2 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxCommandRules.ts
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 type { OperatingSystem } from '../../../base/common/platform.js';
7 > import type { ITerminalSandboxCommand } from './terminalSandboxService.js';
8 >
9 > export interface ITerminalSandboxCommandRuleContext {
10 > readonly os: OperatingSystem;
11 > }
12 >
13 > export interface ITerminalSandboxCommandRule<T> {
14 > readonly keywords: readonly string[];
15 > readonly value: T;
16 > readonly subcommands?: readonly string[];
17 > readonly optionsWithValue?: ReadonlySet<string>;
18 > /** Context-wide guard for rules, such as OS-specific sandbox capabilities. */
19 > readonly condition?: (context: ITerminalSandboxCommandRuleContext) => boolean;
20 > /** Command-specific guard for argument-sensitive rules. */
21 > readonly when?: (command: ITerminalSandboxCommand) => boolean;
22 > }
23 >
24 > export function matchesTerminalSandboxCommandRule<T>(command: ITerminalSandboxCommand, rule: ITerminalSandboxCommandRule<T>, context?: ITerminalSandboxCommandRuleContext): boolean {
25 if (!rule.keywords.includes(command.keyword.toLowerCase())) {
26 return false;
37 return rule.when?.(command) ?? true;
38 }
40 > /**
41 > * Returns the first non-option argument, treating it as the command's subcommand.
42 > * Options are skipped, and options listed in `optionsWithValue` also skip the
43 > * following argument so global option values are not mistaken for subcommands.
44 > *
45 > * For example, with `-C` in `optionsWithValue`, `git -C repo commit` returns
46 > * `commit` instead of `repo`.
47 > */
48 > export function getCommandSubcommand(args: readonly string[], optionsWithValue?: ReadonlySet<string>): string | undefined {
49 for (let i = 0; i < args.length; i++) {
50 const arg = args[i];
src/vs/platform/sandbox/common/terminalSandboxRuntimeConfigurationPerOperation.ts 31 introduced LOC · 5 ranges

Open complete file

1 > /*--------------------------------------------------------------------------------------------- terminalSandboxRuntimeConfigurationPerOperation.ts
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 { OperatingSystem } from '../../../base/common/platform.js';
7 > import type { ITerminalSandboxCommand } from './terminalSandboxService.js';
8 > import { type ITerminalSandboxCommandRule, matchesTerminalSandboxCommandRule } from './terminalSandboxCommandRules.js';
9 >
10 > export const enum TerminalSandboxRuntimeConfigurationOperation {
11 > GnuPG = 'gnupg',
12 > Node = 'node',
13 > }
14 >
15 > const terminalSandboxRuntimeConfigurationCommandRules: readonly ITerminalSandboxCommandRule<TerminalSandboxRuntimeConfigurationOperation>[] = [
16 > {
17 > keywords: ['node', 'npm', 'npx', 'pnpm', 'yarn', 'corepack', 'bun', 'deno', 'nvm', 'volta', 'fnm', 'asdf', 'mise'],
18 > value: TerminalSandboxRuntimeConfigurationOperation.Node,
19 > },
20 > {
21 > keywords: ['git'],
22 > value: TerminalSandboxRuntimeConfigurationOperation.GnuPG,
23 > condition: ({ os }) => os !== OperatingSystem.Windows,
24 > },
25 > ];
26 >
27 function getTerminalSandboxRuntimeConfigurationForOperation(operation: TerminalSandboxRuntimeConfigurationOperation, os: OperatingSystem): Record<string, unknown> {
28 switch (operation) {
66 }
67 }
69 > export function getTerminalSandboxRuntimeConfigurationForCommands(os: OperatingSystem, commandDetails: readonly ITerminalSandboxCommand[]): Record<string, unknown> {
70 const operations = new Set<TerminalSandboxRuntimeConfigurationOperation>();
71 for (const command of commandDetails) {
83 return configuration;
84 }
86 function shouldApplyRuntimeConfigurationOperation(operation: TerminalSandboxRuntimeConfigurationOperation, commandDetails: readonly ITerminalSandboxCommand[]): boolean {
87 switch (operation) {
94 }
95 }
97 function mergeAdditionalSandboxConfigProperties(target: Record<string, unknown>, additional: Record<string, unknown>): void {
98 for (const [key, value] of Object.entries(additional)) {