Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/vs/platform/agentHost/common/sandboxConfigSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const enum AgentHostSandboxKey {
WindowsEnabled = 'enabled.windows',
AllowNetwork = 'allowNetwork',
AllowUnsandboxedCommands = 'allowUnsandboxedCommands',
AutoUpdateSettings = 'autoUpdateSettings',
LinuxFileSystem = 'fileSystem.linux',
MacFileSystem = 'fileSystem.mac',
WindowsFileSystem = 'fileSystem.windows',
Expand All @@ -44,6 +45,7 @@ export type ISandboxConfigValue = Partial<{
[AgentHostSandboxKey.WindowsEnabled]: AgentSandboxEnabledValue;
[AgentHostSandboxKey.AllowNetwork]: boolean;
[AgentHostSandboxKey.AllowUnsandboxedCommands]: boolean;
[AgentHostSandboxKey.AutoUpdateSettings]: boolean;
[AgentHostSandboxKey.LinuxFileSystem]: Record<string, unknown>;
[AgentHostSandboxKey.MacFileSystem]: Record<string, unknown>;
[AgentHostSandboxKey.WindowsFileSystem]: Record<string, unknown>;
Expand Down Expand Up @@ -91,6 +93,10 @@ export const sandboxConfigSchema = createSchema({
type: 'boolean',
title: localize('agentHost.config.sandbox.allowUnsandboxedCommands.title', "Allow Unsandboxed Commands"),
},
[AgentHostSandboxKey.AutoUpdateSettings]: {
type: 'boolean',
title: localize('agentHost.config.sandbox.autoUpdateSettings.title', "Automatically Update Sandbox Configuration"),
},
[AgentHostSandboxKey.LinuxFileSystem]: {
type: 'object',
title: localize('agentHost.config.sandbox.linuxFileSystem.title', "Linux Sandbox Filesystem"),
Expand Down Expand Up @@ -134,6 +140,7 @@ export const sandboxSettingIdToAgentHostKey: Readonly<Record<string, AgentHostSa
[AgentSandboxSettingId.AgentSandboxWindowsEnabled]: AgentHostSandboxKey.WindowsEnabled,
[AgentSandboxSettingId.AgentSandboxAllowNetwork]: AgentHostSandboxKey.AllowNetwork,
[AgentSandboxSettingId.AgentSandboxAllowUnsandboxedCommands]: AgentHostSandboxKey.AllowUnsandboxedCommands,
[AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate]: AgentHostSandboxKey.AutoUpdateSettings,
[AgentSandboxSettingId.AgentSandboxLinuxFileSystem]: AgentHostSandboxKey.LinuxFileSystem,
[AgentSandboxSettingId.AgentSandboxMacFileSystem]: AgentHostSandboxKey.MacFileSystem,
[AgentSandboxSettingId.AgentSandboxWindowsFileSystem]: AgentHostSandboxKey.WindowsFileSystem,
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/sandbox/common/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const enum AgentSandboxSettingId {
AgentSandboxAllowUnsandboxedCommands = 'chat.agent.sandbox.allowUnsandboxedCommands',
AgentSandboxRetryWithAllowNetworkRequests = 'chat.agent.sandbox.retryWithAllowNetworkRequests',
AgentSandboxAllowAutoApprove = 'chat.agent.sandbox.allowAutoApprove',
AgentSandboxSettingsAutoUpdate = 'chat.agent.sandbox.settings.autoUpdate',
AgentSandboxLinuxFileSystem = 'chat.agent.sandbox.fileSystem.linux',
AgentSandboxMacFileSystem = 'chat.agent.sandbox.fileSystem.mac',
AgentSandboxWindowsFileSystem = 'chat.agent.sandbox.fileSystem.windows',
Expand Down
25 changes: 19 additions & 6 deletions src/vs/platform/sandbox/common/terminalSandboxEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,14 @@ export class TerminalSandboxEngine extends Disposable {
const blockedDomainResult = shouldInspectBlockedDomains ? this._getBlockedDomains(command) : { blockedDomains: [], deniedDomains: [] };
const requiresPreflightAllowNetwork = retryWithAllowNetworkRequests && blockedDomainResult.blockedDomains.length > 0;
const allowNetworkForCommand = requestUnsandboxedExecution !== true && ((requestAllowNetwork === true && retryWithAllowNetworkRequests) || requiresPreflightAllowNetwork);
const normalizedCommandDetails = this._normalizeCommandDetails(commandDetails ?? []);
const autoUpdateSandboxSettings = this._isSandboxSettingsAutoUpdateEnabled();
const normalizedCommandDetails = autoUpdateSandboxSettings ? this._normalizeCommandDetails(commandDetails ?? []) : [];
const normalizedCommandKeywords = this._normalizeCommandKeywords(normalizedCommandDetails.map(c => c.keyword));
const currentReadAllowListPaths = getTerminalSandboxReadAllowListForCommands(this._os, this._commandAllowListKeywords, this._commandAllowListCommandDetails);
const currentCommandKeywords = autoUpdateSandboxSettings ? this._commandAllowListKeywords : [];
const currentCommandDetails = autoUpdateSandboxSettings ? this._commandAllowListCommandDetails : [];
const currentReadAllowListPaths = getTerminalSandboxReadAllowListForCommands(this._os, currentCommandKeywords, currentCommandDetails);
const nextReadAllowListPaths = getTerminalSandboxReadAllowListForCommands(this._os, normalizedCommandKeywords, normalizedCommandDetails);
const currentRuntimeConfiguration = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
const currentRuntimeConfiguration = getTerminalSandboxRuntimeConfigurationForCommands(this._os, currentCommandDetails);
const nextRuntimeConfiguration = getTerminalSandboxRuntimeConfigurationForCommands(this._os, normalizedCommandDetails);
const shouldRefreshConfig = this._commandAllowListKeywords.length === 0
|| this._needsForceUpdateConfigFile
Expand Down Expand Up @@ -641,7 +644,7 @@ export class TerminalSandboxEngine extends Disposable {
? this._getSettingValue<string>(AgentSandboxSettingId.AgentSandboxWindowsSchemaVersion)
: undefined;
const runtimeSetting = this._getSettingValue<Record<string, unknown>>(AgentSandboxSettingId.AgentSandboxAdvancedRuntime) ?? {};
const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._getCommandDetailsForSandboxConfig());
const commandRuntimeAllowReadPaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowRead');
const commandRuntimeAllowWritePaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowWrite');
const configFileUri = URI.joinPath(this._tempDir, `vscode-sandbox-settings-${this._sandboxSettingsId}.json`);
Expand Down Expand Up @@ -714,7 +717,7 @@ export class TerminalSandboxEngine extends Disposable {
const windowsFileSystemSetting = this._os === OperatingSystem.Windows
? this._getSettingValue<ITerminalSandboxFileSystemSetting>(AgentSandboxSettingId.AgentSandboxWindowsFileSystem) ?? {}
: {};
const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._commandAllowListCommandDetails);
const commandRuntimeSetting = getTerminalSandboxRuntimeConfigurationForCommands(this._os, this._getCommandDetailsForSandboxConfig());
const commandRuntimeAllowReadPaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowRead');
const commandRuntimeAllowWritePaths = this._getCommandRuntimeFileSystemPaths(commandRuntimeSetting, 'allowWrite');
let allowWritePaths: string[] = [];
Expand Down Expand Up @@ -929,7 +932,9 @@ export class TerminalSandboxEngine extends Disposable {
}

private async _updateAllowReadPathsWithAllowWrite(configuredAllowRead: string[] | undefined, allowWrite: string[], commandRuntimeAllowRead: string[] = []): Promise<string[]> {
return [...new Set([...(configuredAllowRead ?? []), ...getTerminalSandboxReadAllowListForCommands(this._os, this._commandAllowListKeywords, this._commandAllowListCommandDetails), ...commandRuntimeAllowRead, ...this._getSandboxRuntimeReadPaths(), ...await this._getWorkspaceStorageReadPaths(), ...allowWrite])];
const commandDetails = this._getCommandDetailsForSandboxConfig();
const commandKeywords = this._isSandboxSettingsAutoUpdateEnabled() ? this._commandAllowListKeywords : [];
return [...new Set([...(configuredAllowRead ?? []), ...getTerminalSandboxReadAllowListForCommands(this._os, commandKeywords, commandDetails), ...commandRuntimeAllowRead, ...this._getSandboxRuntimeReadPaths(), ...await this._getWorkspaceStorageReadPaths(), ...allowWrite])];
}

private async _resolveFileSystemPaths(paths: string[] | undefined): Promise<string[]> {
Expand Down Expand Up @@ -1077,6 +1082,14 @@ export class TerminalSandboxEngine extends Disposable {
return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxRetryWithAllowNetworkRequests) === true;
}

private _isSandboxSettingsAutoUpdateEnabled(): boolean {
return this._getSettingValue<boolean>(AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate) !== false;
}

private _getCommandDetailsForSandboxConfig(): readonly ITerminalSandboxCommand[] {
return this._isSandboxSettingsAutoUpdateEnabled() ? this._commandAllowListCommandDetails : [];
}

private _getSettingValue<T>(settingId: AgentSandboxSettingId | AgentNetworkDomainSettingId): T | undefined {
return this._host.getSandboxSetting<T>(settingId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const SANDBOX_SETTING_KEYS: readonly string[] = [
AgentSandboxSettingId.AgentSandboxWindowsEnabled,
AgentSandboxSettingId.AgentSandboxAllowNetwork,
AgentSandboxSettingId.AgentSandboxAllowUnsandboxedCommands,
AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate,
AgentSandboxSettingId.AgentSandboxLinuxFileSystem,
AgentSandboxSettingId.AgentSandboxMacFileSystem,
AgentSandboxSettingId.AgentSandboxWindowsFileSystem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,13 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
}
}
},
[AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate]: {
markdownDescription: localize('agentSandbox.autoUpdate', "Controls whether the effective sandbox configuration is automatically adjusted based on the terminal command being executed. When disabled, only explicitly configured sandbox settings are used. This applies only when {0} is enabled.", `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``),
type: 'boolean',
default: true,
tags: ['preview'],
restricted: true
},
[TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem]: {
markdownDescription: localize('agentSandbox.linuxFileSystemSetting', "Note: this setting is applicable only when {0} is enabled. Controls file system access in sandbox on Linux. Paths do not support glob patterns, only literal paths (ex: ./src/, ~/.ssh, .env). **bubblewrap** and **socat** should be installed for this setting to work.", `\`#${AgentSandboxSettingId.AgentSandboxEnabled}#\``),
type: 'object',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,27 @@ suite('TerminalSandboxService - network domains', () => {
ok(!gitConfig.filesystem.allowWrite.includes('/home/user/.volta/'), 'Refreshing for a new command should start allowWrite from the current command details');
});

test('should not add command-specific sandbox settings when auto update is disabled', async () => {
configurationService.setUserConfiguration(AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate, false);
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();

ok(configPath, 'Config path should be defined');
await sandboxService.wrapCommand('node --version && git status', false, 'bash', undefined, [
{ keyword: 'node', args: ['--version'] },
{ keyword: 'git', args: ['status'] },
]);
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be rewritten for the command');

const config = JSON.parse(configContent);
ok(!config.filesystem.allowRead.includes('/home/user/.nvm/versions'), 'Disabled auto update should omit Node read paths');
ok(!config.filesystem.allowWrite.includes('/home/user/.volta/'), 'Disabled auto update should omit Node write paths');
ok(!config.filesystem.allowRead.includes('/home/user/.gitconfig'), 'Disabled auto update should omit Git read paths');
ok(!config.filesystem.allowRead.includes('/home/user/.gnupg'), 'Disabled auto update should omit command runtime read paths');
ok(!config.filesystem.allowWrite.includes('/home/user/.gnupg'), 'Disabled auto update should omit command runtime write paths');
Comment on lines +864 to +865
});

test('should apply command-detail read and write allow-list rules', async () => {
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
Expand Down
Loading