From 12c8330620a89972b1ad589ff3f65a563a123680 Mon Sep 17 00:00:00 2001 From: Dileep Yavanmandha Date: Fri, 24 Jul 2026 16:25:42 -0700 Subject: [PATCH] Add sandbox settings auto-update control --- .../agentHost/common/sandboxConfigSchema.ts | 7 ++++++ src/vs/platform/sandbox/common/settings.ts | 1 + .../sandbox/common/terminalSandboxEngine.ts | 25 ++++++++++++++----- .../common/sandboxSettingsReader.ts | 1 + .../terminalChatAgentToolsConfiguration.ts | 7 ++++++ .../browser/terminalSandboxService.test.ts | 21 ++++++++++++++++ 6 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/agentHost/common/sandboxConfigSchema.ts b/src/vs/platform/agentHost/common/sandboxConfigSchema.ts index 2b834e0684f1fb..a5f67bdf3d97cb 100644 --- a/src/vs/platform/agentHost/common/sandboxConfigSchema.ts +++ b/src/vs/platform/agentHost/common/sandboxConfigSchema.ts @@ -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', @@ -44,6 +45,7 @@ export type ISandboxConfigValue = Partial<{ [AgentHostSandboxKey.WindowsEnabled]: AgentSandboxEnabledValue; [AgentHostSandboxKey.AllowNetwork]: boolean; [AgentHostSandboxKey.AllowUnsandboxedCommands]: boolean; + [AgentHostSandboxKey.AutoUpdateSettings]: boolean; [AgentHostSandboxKey.LinuxFileSystem]: Record; [AgentHostSandboxKey.MacFileSystem]: Record; [AgentHostSandboxKey.WindowsFileSystem]: Record; @@ -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"), @@ -134,6 +140,7 @@ export const sandboxSettingIdToAgentHostKey: Readonly 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 @@ -641,7 +644,7 @@ export class TerminalSandboxEngine extends Disposable { ? this._getSettingValue(AgentSandboxSettingId.AgentSandboxWindowsSchemaVersion) : undefined; const runtimeSetting = this._getSettingValue>(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`); @@ -714,7 +717,7 @@ export class TerminalSandboxEngine extends Disposable { const windowsFileSystemSetting = this._os === OperatingSystem.Windows ? this._getSettingValue(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[] = []; @@ -929,7 +932,9 @@ export class TerminalSandboxEngine extends Disposable { } private async _updateAllowReadPathsWithAllowWrite(configuredAllowRead: string[] | undefined, allowWrite: string[], commandRuntimeAllowRead: string[] = []): Promise { - 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 { @@ -1077,6 +1082,14 @@ export class TerminalSandboxEngine extends Disposable { return this._getSettingValue(AgentSandboxSettingId.AgentSandboxRetryWithAllowNetworkRequests) === true; } + private _isSandboxSettingsAutoUpdateEnabled(): boolean { + return this._getSettingValue(AgentSandboxSettingId.AgentSandboxSettingsAutoUpdate) !== false; + } + + private _getCommandDetailsForSandboxConfig(): readonly ITerminalSandboxCommand[] { + return this._isSandboxSettingsAutoUpdateEnabled() ? this._commandAllowListCommandDetails : []; + } + private _getSettingValue(settingId: AgentSandboxSettingId | AgentNetworkDomainSettingId): T | undefined { return this._host.getSandboxSetting(settingId); } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/sandboxSettingsReader.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/sandboxSettingsReader.ts index 4a2f61e2b20559..91fbb9a696b1fc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/sandboxSettingsReader.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/sandboxSettingsReader.ts @@ -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, diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts index fdfef11b7e448d..5e05861daad092 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalChatAgentToolsConfiguration.ts @@ -641,6 +641,13 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary { 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'); + }); + test('should apply command-detail read and write allow-list rules', async () => { const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService)); const configPath = await sandboxService.getSandboxConfigPath();