mirror of
https://github.com/microsoft/vscode.git
synced 2026-06-29 02:45:58 +01:00
Updates for windows sandboxing (#323062)
This commit is contained in:
Generated
+4
-4
@@ -20,7 +20,7 @@
|
||||
"@microsoft/dev-tunnels-management": "^1.3.41",
|
||||
"@microsoft/dev-tunnels-ssh": "^3.12.22",
|
||||
"@microsoft/dev-tunnels-ssh-tcp": "^3.12.22",
|
||||
"@microsoft/mxc-sdk": "0.6.0",
|
||||
"@microsoft/mxc-sdk": "0.6.1",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/codicons": "^0.0.46-21",
|
||||
@@ -1911,9 +1911,9 @@
|
||||
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.6.0.tgz",
|
||||
"integrity": "sha512-O+cKLjO4mE/D4dDp2GmVJ8hAj43vQHLf1YTMUWUtU4+41ddThhb1SYkn6W9b3FLl63bJW/4dqReJG6PIBk8jqQ==",
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.6.1.tgz",
|
||||
"integrity": "sha512-jpbJU/xfF4qLWcNMplDTUX/q13m2A6vYao1QN3lkZaQlzsRce95H+iU0Qu0wlweJZ2gx6eY1PRQU+/bnQki/dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-pty": "^1.2.0-beta.12",
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@
|
||||
"@microsoft/dev-tunnels-management": "^1.3.41",
|
||||
"@microsoft/dev-tunnels-ssh": "^3.12.22",
|
||||
"@microsoft/dev-tunnels-ssh-tcp": "^3.12.22",
|
||||
"@microsoft/mxc-sdk": "0.6.0",
|
||||
"@microsoft/mxc-sdk": "0.6.1",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vscode/codicons": "^0.0.46-21",
|
||||
|
||||
Generated
+4
-4
@@ -12,7 +12,7 @@
|
||||
"@github/copilot-sdk": "^1.0.4",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@microsoft/mxc-sdk": "0.6.0",
|
||||
"@microsoft/mxc-sdk": "0.6.1",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@vscode/copilot-api": "^0.4.2",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
@@ -287,9 +287,9 @@
|
||||
"integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ=="
|
||||
},
|
||||
"node_modules/@microsoft/mxc-sdk": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.6.0.tgz",
|
||||
"integrity": "sha512-O+cKLjO4mE/D4dDp2GmVJ8hAj43vQHLf1YTMUWUtU4+41ddThhb1SYkn6W9b3FLl63bJW/4dqReJG6PIBk8jqQ==",
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@microsoft/mxc-sdk/-/mxc-sdk-0.6.1.tgz",
|
||||
"integrity": "sha512-jpbJU/xfF4qLWcNMplDTUX/q13m2A6vYao1QN3lkZaQlzsRce95H+iU0Qu0wlweJZ2gx6eY1PRQU+/bnQki/dw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-pty": "^1.2.0-beta.12",
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
"@github/copilot-sdk": "^1.0.4",
|
||||
"@microsoft/1ds-core-js": "^3.2.13",
|
||||
"@microsoft/1ds-post-js": "^3.2.13",
|
||||
"@microsoft/mxc-sdk": "0.6.0",
|
||||
"@microsoft/mxc-sdk": "0.6.1",
|
||||
"@parcel/watcher": "^2.5.6",
|
||||
"@vscode/copilot-api": "^0.4.2",
|
||||
"@vscode/deviceid": "^0.1.1",
|
||||
|
||||
@@ -59,7 +59,6 @@ export interface IWindowsMxcConfig {
|
||||
timeout?: number;
|
||||
};
|
||||
processContainer?: {
|
||||
name?: string;
|
||||
leastPrivilege?: boolean;
|
||||
capabilities?: string[];
|
||||
ui?: {
|
||||
|
||||
@@ -665,7 +665,6 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
tempDir: this._tempDir,
|
||||
schemaVersion: windowsSchemaVersion,
|
||||
allowNetwork,
|
||||
networkDomains: this.getResolvedNetworkDomains(),
|
||||
allowReadPaths,
|
||||
allowWritePaths,
|
||||
denyReadPaths,
|
||||
@@ -928,7 +927,19 @@ export class TerminalSandboxEngine extends Disposable {
|
||||
|
||||
private async _resolveFileSystemPaths(paths: string[] | undefined): Promise<string[]> {
|
||||
const resolvedPaths = await Promise.all((paths ?? []).map(path => this._resolveFileSystemPath(path)));
|
||||
return [...new Set(resolvedPaths.flat())];
|
||||
const seenPaths = new Set<string>();
|
||||
return resolvedPaths.flat().filter(path => {
|
||||
const comparisonKey = this._getFileSystemPathComparisonKey(path);
|
||||
if (seenPaths.has(comparisonKey)) {
|
||||
return false;
|
||||
}
|
||||
seenPaths.add(comparisonKey);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private _getFileSystemPathComparisonKey(path: string): string {
|
||||
return this._os === OperatingSystem.Windows ? path.replace(/\//g, '\\').toLowerCase() : path;
|
||||
}
|
||||
|
||||
private async _resolveFileSystemPath(path: string): Promise<string[]> {
|
||||
|
||||
@@ -7,7 +7,6 @@ import { win32 } from '../../../base/common/path.js';
|
||||
import { URI } from '../../../base/common/uri.js';
|
||||
import { createDecorator } from '../../instantiation/common/instantiation.js';
|
||||
import type { IWindowsMxcConfig, IWindowsMxcPolicyContainment, IWindowsMxcSandboxPolicy } from './sandboxHelperService.js';
|
||||
import type { ITerminalSandboxResolvedNetworkDomains } from './terminalSandboxService.js';
|
||||
|
||||
export interface IWindowsMxcConfigOptions {
|
||||
command: string;
|
||||
@@ -16,7 +15,6 @@ export interface IWindowsMxcConfigOptions {
|
||||
tempDir: URI;
|
||||
schemaVersion?: string;
|
||||
allowNetwork: boolean;
|
||||
networkDomains: ITerminalSandboxResolvedNetworkDomains;
|
||||
allowReadPaths: string[];
|
||||
allowWritePaths: string[];
|
||||
denyReadPaths: string[];
|
||||
@@ -47,8 +45,7 @@ export interface IWindowsMxcTerminalSandboxRuntime {
|
||||
export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSandboxRuntime {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _configVersion = '0.4.0-alpha';
|
||||
private readonly _containerName = 'vscode-terminal-sandbox';
|
||||
private readonly _configVersion = '0.6.0-alpha';
|
||||
|
||||
getExecutablePath(appRoot: string, arch: string | undefined): string {
|
||||
const binArch = arch === 'arm64' ? 'arm64' : 'x64';
|
||||
@@ -71,17 +68,17 @@ export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSand
|
||||
const shell = options.shell
|
||||
? this._quoteWindowsCommandLineArgument(options.shell)
|
||||
: 'pwsh.exe';
|
||||
const commandLine = `${shell} -NoProfile -ExecutionPolicy Bypass -Command ${this._quoteWindowsCommandLineArgument(options.command)}`;
|
||||
const commandLine = `${shell} -NoProfile -Command ${this._quoteWindowsCommandLineArgument(options.command)}`;
|
||||
const cwd = options.cwd ? this.toWindowsPath(options.cwd) : tempDirPath;
|
||||
const policy: IWindowsMxcSandboxPolicy = {
|
||||
version: options.schemaVersion ?? this._configVersion,
|
||||
timeoutMs: 0,
|
||||
filesystem: {
|
||||
readwritePaths: [...new Set(options.allowWritePaths.map(path => this._normalizeWindowsPath(path)))],
|
||||
readonlyPaths: [...new Set([tempDirPath, ...(options.shell && win32.isAbsolute(options.shell) ? [win32.dirname(options.shell)] : []), ...options.allowReadPaths].map(path => this._normalizeWindowsPath(path)))],
|
||||
deniedPaths: [...new Set(options.denyReadPaths.map(path => this._normalizeWindowsPath(path)))],
|
||||
readwritePaths: options.allowWritePaths.map(path => this._normalizeWindowsPath(path)),
|
||||
readonlyPaths: [tempDirPath, ...(options.shell && win32.isAbsolute(options.shell) ? [win32.dirname(options.shell)] : []), ...options.allowReadPaths].map(path => this._normalizeWindowsPath(path)),
|
||||
deniedPaths: options.denyReadPaths.map(path => this._normalizeWindowsPath(path)),
|
||||
},
|
||||
network: this._createNetworkPolicy(options.allowNetwork, options.networkDomains),
|
||||
network: this._createNetworkPolicy(options.allowNetwork),
|
||||
ui: {
|
||||
allowWindows: true,
|
||||
clipboard: 'none',
|
||||
@@ -89,7 +86,7 @@ export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSand
|
||||
},
|
||||
};
|
||||
|
||||
const config = await buildSandboxPayload(commandLine, policy, cwd, this._containerName);
|
||||
const config = await buildSandboxPayload(commandLine, policy, cwd);
|
||||
if (!config?.process) {
|
||||
throw new Error('Unable to build Windows MXC sandbox payload');
|
||||
}
|
||||
@@ -123,20 +120,10 @@ export class WindowsMxcTerminalSandboxRuntime implements IWindowsMxcTerminalSand
|
||||
return path.replace(/\//g, '\\');
|
||||
}
|
||||
|
||||
private _createNetworkPolicy(allowNetwork: boolean, networkDomains: ITerminalSandboxResolvedNetworkDomains): NonNullable<IWindowsMxcSandboxPolicy['network']> {
|
||||
const allowedHosts = networkDomains.allowedDomains.length > 0 ? networkDomains.allowedDomains : undefined;
|
||||
const blockedHosts = networkDomains.deniedDomains.length > 0 ? networkDomains.deniedDomains : undefined;
|
||||
const allowOutbound = allowNetwork || !!allowedHosts?.length;
|
||||
const network: NonNullable<IWindowsMxcSandboxPolicy['network']> = {
|
||||
allowOutbound,
|
||||
};
|
||||
if (allowOutbound && allowedHosts) {
|
||||
network.allowedHosts = allowedHosts;
|
||||
}
|
||||
if (allowOutbound && blockedHosts) {
|
||||
network.blockedHosts = blockedHosts;
|
||||
}
|
||||
return network;
|
||||
private _createNetworkPolicy(allowNetwork: boolean): NonNullable<IWindowsMxcSandboxPolicy['network']> {
|
||||
// MXC does not support per-host network policies on Windows. Rely on the
|
||||
// overall allow/block policy instead of emitting unsupported host lists.
|
||||
return { allowOutbound: allowNetwork };
|
||||
}
|
||||
|
||||
private _quotePowerShellArgument(value: string): string {
|
||||
|
||||
@@ -70,9 +70,7 @@ suite('TerminalSandboxEngine', () => {
|
||||
const network = {
|
||||
defaultPolicy: policy.network?.allowOutbound ? 'allow' : 'block' as 'allow' | 'block',
|
||||
...(policy.network?.allowLocalNetwork !== undefined ? { allowLocalNetwork: policy.network.allowLocalNetwork } : {}),
|
||||
...(policy.network?.allowedHosts ? { allowedHosts: policy.network.allowedHosts } : {}),
|
||||
...(policy.network?.blockedHosts ? { blockedHosts: policy.network.blockedHosts } : {}),
|
||||
...(policy.network ? { enforcementMode: policy.network.allowedHosts?.length || policy.network.blockedHosts?.length ? 'both' as const : 'capabilities' as const } : {}),
|
||||
...(policy.network ? { enforcementMode: 'capabilities' as const } : {}),
|
||||
};
|
||||
return {
|
||||
version: policy.version,
|
||||
@@ -88,7 +86,6 @@ suite('TerminalSandboxEngine', () => {
|
||||
timeout: policy.timeoutMs ?? 0,
|
||||
},
|
||||
processContainer: {
|
||||
name: containerName,
|
||||
leastPrivilege: false,
|
||||
capabilities: policy.network?.allowOutbound ? ['internetClient'] : [],
|
||||
ui: {
|
||||
@@ -527,10 +524,9 @@ suite('TerminalSandboxEngine', () => {
|
||||
strictEqual(wrapped.isSandboxWrapped, true);
|
||||
ok(wrapped.command.startsWith(`& 'C:\\app\\node_modules\\@microsoft\\mxc-sdk\\bin\\x64\\wxc-exec.exe'`), `Expected MXC executable. Actual: ${wrapped.command}`);
|
||||
ok(wrapped.command.includes(` '${configPath}'`), `Expected wrapped command to pass the MXC config path. Actual: ${wrapped.command}`);
|
||||
strictEqual(config.version, '0.4.0-alpha');
|
||||
strictEqual(config.version, '0.6.0-alpha');
|
||||
strictEqual(config.containment, 'process');
|
||||
strictEqual(config.processContainer.name, 'vscode-terminal-sandbox');
|
||||
strictEqual(config.process.commandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -Command "echo hello"');
|
||||
strictEqual(config.process.commandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo hello"');
|
||||
strictEqual(normalizeWindowsPathForAssert(config.process.cwd), 'c:/workspace');
|
||||
strictEqual(config.ui.disable, false);
|
||||
ok(config.process.env.includes('SystemRoot=C:\\Windows'), 'SystemRoot should be injected into the MXC process env');
|
||||
@@ -579,6 +575,54 @@ suite('TerminalSandboxEngine', () => {
|
||||
ok(!config.filesystem.deniedPaths.some((path: string) => normalizeWindowsPathForAssert(path) === 'c:/users/user'), 'User home should not be denied by default on Windows');
|
||||
});
|
||||
|
||||
test('deduplicates Windows filesystem paths regardless of case or separator', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsFileSystem, {
|
||||
allowWrite: ['C:/configured/write'],
|
||||
allowRead: ['C:\\configured\\read'],
|
||||
denyRead: ['C:/configured/secret', 'c:\\configured\\secret'],
|
||||
});
|
||||
const host = createWindowsHost({
|
||||
getWindowsMxcFilesystemPolicy: () => Promise.resolve({
|
||||
readwritePaths: ['c:\\configured\\write'],
|
||||
readonlyPaths: ['c:/configured/read'],
|
||||
}),
|
||||
});
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('echo hello', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
const matchingPaths = (paths: string[], expectedPath: string) => paths.filter(path => normalizeWindowsPathForAssert(path) === expectedPath);
|
||||
|
||||
deepStrictEqual({
|
||||
readwrite: matchingPaths(config.filesystem.readwritePaths, 'c:/configured/write'),
|
||||
readonly: matchingPaths(config.filesystem.readonlyPaths, 'c:/configured/read'),
|
||||
denied: matchingPaths(config.filesystem.deniedPaths, 'c:/configured/secret'),
|
||||
}, {
|
||||
readwrite: ['C:\\configured\\write'],
|
||||
readonly: ['C:\\configured\\read'],
|
||||
denied: ['C:\\configured\\secret'],
|
||||
});
|
||||
});
|
||||
|
||||
test('deduplicates resolved Windows paths regardless of case or separator', async () => {
|
||||
enableWindowsSandbox();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createWindowsHost()));
|
||||
await engine.getOS();
|
||||
const resolveFileSystemPaths = (engine as unknown as { _resolveFileSystemPaths(paths: string[]): Promise<string[]> })._resolveFileSystemPaths.bind(engine);
|
||||
|
||||
deepStrictEqual(await resolveFileSystemPaths([
|
||||
'C:/configured/path',
|
||||
'c:\\configured\\path',
|
||||
'C:\\configured\\other-path',
|
||||
]), [
|
||||
'C:/configured/path',
|
||||
'C:\\configured\\other-path',
|
||||
]);
|
||||
});
|
||||
|
||||
test('wrapCommand applies configured Windows MXC schema version', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentSandboxSettingId.AgentSandboxWindowsSchemaVersion, '0.5.0-alpha');
|
||||
@@ -651,13 +695,13 @@ suite('TerminalSandboxEngine', () => {
|
||||
let configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const firstCommandLine = JSON.parse(createdFiles.get(configPath)!).process.commandLine;
|
||||
strictEqual(firstCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -Command "echo first"');
|
||||
strictEqual(firstCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo first"');
|
||||
|
||||
await engine.wrapCommand('echo second', false, 'C:\\Program Files\\PowerShell\\7\\pwsh.exe');
|
||||
configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const secondCommandLine = JSON.parse(createdFiles.get(configPath)!).process.commandLine;
|
||||
strictEqual(secondCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -Command "echo second"');
|
||||
strictEqual(secondCommandLine, '"C:\\Program Files\\PowerShell\\7\\pwsh.exe" -NoProfile -Command "echo second"');
|
||||
});
|
||||
|
||||
test('allowNetwork maps to MXC allow network config on Windows', async () => {
|
||||
@@ -674,6 +718,21 @@ suite('TerminalSandboxEngine', () => {
|
||||
deepStrictEqual(config.network, { defaultPolicy: 'allow', enforcementMode: 'capabilities' });
|
||||
});
|
||||
|
||||
test('Windows MXC config ignores unsupported network host lists', async () => {
|
||||
enableWindowsSandbox();
|
||||
setSandboxSetting(AgentNetworkDomainSettingId.AllowedNetworkDomains, ['example.com']);
|
||||
setSandboxSetting(AgentNetworkDomainSettingId.DeniedNetworkDomains, ['blocked.example.com']);
|
||||
const host = createWindowsHost();
|
||||
const engine = store.add(instantiationService.createInstance(TerminalSandboxEngine, host));
|
||||
|
||||
await engine.wrapCommand('curl https://example.com', false, 'pwsh');
|
||||
const configPath = await engine.getSandboxConfigPath();
|
||||
ok(configPath, 'Config path should be defined');
|
||||
const config = JSON.parse(createdFiles.get(configPath)!);
|
||||
|
||||
deepStrictEqual(config.network, { defaultPolicy: 'allow', enforcementMode: 'capabilities' });
|
||||
});
|
||||
|
||||
test('uses OS-specific filesystem absolute path detection', async () => {
|
||||
const linuxEngine = store.add(instantiationService.createInstance(TerminalSandboxEngine, createHost()));
|
||||
await linuxEngine.getOS();
|
||||
|
||||
+3
-6
@@ -225,9 +225,7 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
},
|
||||
network: {
|
||||
defaultPolicy: policy.network?.allowOutbound ? 'allow' : 'block',
|
||||
...(policy.network ? { enforcementMode: policy.network.allowedHosts?.length || policy.network.blockedHosts?.length ? 'both' : 'capabilities' } : {}),
|
||||
...(policy.network?.allowedHosts ? { allowedHosts: policy.network.allowedHosts } : {}),
|
||||
...(policy.network?.blockedHosts ? { blockedHosts: policy.network.blockedHosts } : {}),
|
||||
...(policy.network ? { enforcementMode: 'capabilities' } : {}),
|
||||
},
|
||||
ui: {
|
||||
disable: !(policy.ui?.allowWindows ?? false),
|
||||
@@ -1458,10 +1456,9 @@ suite('TerminalSandboxService - network domains', () => {
|
||||
strictEqual(wrapped.isSandboxWrapped, true);
|
||||
ok(wrapped.command.includes('node_modules\\@microsoft\\mxc-sdk\\bin\\arm64\\wxc-exec.exe'), `Wrapped command should use the MXC Windows executable. Actual: ${wrapped.command}`);
|
||||
ok(wrapped.command.includes(configPath), `Wrapped command should pass the MXC config path. Actual: ${wrapped.command}`);
|
||||
strictEqual(config.version, '0.4.0-alpha');
|
||||
strictEqual(config.version, '0.6.0-alpha');
|
||||
strictEqual(config.containment, 'process');
|
||||
strictEqual(config.processContainer.name, 'vscode-terminal-sandbox');
|
||||
strictEqual(config.process.commandLine, '"c:\\program files\\powershell\\7\\pwsh.exe" -NoProfile -ExecutionPolicy Bypass -Command "echo test"');
|
||||
strictEqual(config.process.commandLine, '"c:\\program files\\powershell\\7\\pwsh.exe" -NoProfile -Command "echo test"');
|
||||
strictEqual(config.process.cwd, 'c:\\workspace-one');
|
||||
ok(config.process.env.includes('SystemRoot=c:\\windows'), 'SystemRoot should be injected into the MXC process env');
|
||||
ok(config.process.env.includes('PATH=c:\\tools\\node;c:\\windows\\system32'), 'PATH should be injected into the MXC process env');
|
||||
|
||||
Reference in New Issue
Block a user