Rename sandbox setting to chat.agent.sandbox (#303421) (#305846)

* Rename sandbox setting to chat.agent.sandbox (#303421)

Rename the top-level sandbox setting from `chat.tools.terminal.sandbox.enabled`
to `chat.agent.sandbox` to reflect that sandboxing is a general agent concept,
not terminal-specific.

- Update setting ID value to `chat.agent.sandbox`
- Update description to be more general
- Deprecate old `chat.tools.terminal.sandbox.enabled` setting
- Update telemetry event name

Fixes #303421

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* updating terminal sandbox to agent sandbox

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
dileepyavan
2026-03-28 18:31:02 -07:00
committed by GitHub
parent 6fadff8258
commit 57d9535056
10 changed files with 201 additions and 291 deletions

View File

@@ -15,8 +15,8 @@ const enum ShellIntegrationTimeoutOverride {
const isWindows = process.platform === 'win32';
const isMacOS = process.platform === 'darwin';
const sandboxFileSystemSetting = isMacOS
? 'chat.tools.terminal.sandbox.macFileSystem'
: 'chat.tools.terminal.sandbox.linuxFileSystem';
? 'chat.agent.sandboxFileSystem.mac'
: 'chat.agent.sandboxFileSystem.linux';
/**
* Extracts all text content from a LanguageModelToolResult.
@@ -297,13 +297,13 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
(isWindows ? suite.skip : suite)('sandbox on', () => {
setup(async () => {
const sandboxConfig = vscode.workspace.getConfiguration('chat.tools.terminal.sandbox');
await sandboxConfig.update('enabled', true, vscode.ConfigurationTarget.Global);
const configuration = vscode.workspace.getConfiguration();
await configuration.update('chat.agent.sandbox', true, vscode.ConfigurationTarget.Global);
});
teardown(async () => {
const sandboxConfig = vscode.workspace.getConfiguration('chat.tools.terminal.sandbox');
await sandboxConfig.update('enabled', undefined, vscode.ConfigurationTarget.Global);
const configuration = vscode.workspace.getConfiguration();
await configuration.update('chat.agent.sandbox', undefined, vscode.ConfigurationTarget.Global);
});
test('echo works in sandbox and output is clean', async function () {
@@ -326,7 +326,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
const acceptable = [
[
'Command failed while running in sandboxed mode. If the command failed due to sandboxing:',
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.`,
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.agent.sandboxNetwork.allowedDomains.`,
'- Otherwise, immediately retry the command with requestUnsandboxedExecution=true. Do NOT ask the user \u2014 setting this flag automatically shows a confirmation prompt to the user.',
'',
'Here is the output of the command:',
@@ -375,7 +375,7 @@ function extractTextContent(result: vscode.LanguageModelToolResult): string {
? `/bin/bash: /tmp/${marker}.txt: Operation not permitted`
: `/usr/bin/bash: line 1: /tmp/${marker}.txt: Read-only file system`;
const sandboxBody = [
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.tools.terminal.sandbox.network.allowedDomains.`,
`- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${sandboxFileSystemSetting}, or to add required domains to chat.agent.sandboxNetwork.allowedDomains.`,
'- Otherwise, immediately retry the command with requestUnsandboxedExecution=true. Do NOT ask the user \u2014 setting this flag automatically shows a confirmation prompt to the user.',
'',
'Here is the output of the command:',

View File

@@ -444,13 +444,13 @@ class ConfigurationTelemetryContribution extends Disposable implements IWorkbenc
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source of the setting' };
}>('terminal.integrated.suggest.enabled', { settingValue: this.getValueToReport(key, target), source });
return;
case TerminalContribSettingId.TerminalSandboxEnabled:
case TerminalContribSettingId.AgentSandboxEnabled:
this.telemetryService.publicLog2<UpdatedSettingEvent, {
owner: 'isidorn';
comment: 'This is used to know if terminal sandbox is enabled or not';
comment: 'This is used to know if agent sandbox is enabled or not';
settingValue: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'value of the setting' };
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source of the setting' };
}>('chat.tools.terminal.sandbox.enabled', { settingValue: this.getValueToReport(key, target), source });
}>('chat.agent.sandbox', { settingValue: this.getValueToReport(key, target), source });
return;
}
}

View File

@@ -728,19 +728,67 @@ Registry.as<IConfigurationMigrationRegistry>(WorkbenchExtensions.ConfigurationMi
Registry.as<IConfigurationMigrationRegistry>(WorkbenchExtensions.ConfigurationMigration)
.registerConfigurationMigrations([{
key: TerminalContribSettingId.DeprecatedTerminalSandboxNetwork,
migrateFn: (value: { allowedDomains?: string[]; deniedDomains?: string[]; allowTrustedDomains?: boolean }, valueAccessor) => {
key: TerminalContribSettingId.DeprecatedTerminalSandboxEnabled,
migrateFn: (value: boolean, valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value?.allowedDomains !== undefined && valueAccessor(TerminalContribSettingId.TerminalSandboxNetworkAllowedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.TerminalSandboxNetworkAllowedDomains, { value: value.allowedDomains }]);
if (value !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxEnabled) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxEnabled, { value }]);
}
if (value?.deniedDomains !== undefined && valueAccessor(TerminalContribSettingId.TerminalSandboxNetworkDeniedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.TerminalSandboxNetworkDeniedDomains, { value: value.deniedDomains }]);
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxEnabled, { value: undefined }]);
return configurationKeyValuePairs;
}
}, {
key: TerminalContribSettingId.DeprecatedTerminalSandboxNetwork,
migrateFn: (value: { allowedDomains?: string[]; deniedDomains?: string[] }, valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value?.allowedDomains !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxNetworkAllowedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxNetworkAllowedDomains, { value: value.allowedDomains }]);
}
if (value?.allowTrustedDomains !== undefined && valueAccessor(TerminalContribSettingId.TerminalSandboxNetworkAllowTrustedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.TerminalSandboxNetworkAllowTrustedDomains, { value: value.allowTrustedDomains }]);
if (value?.deniedDomains !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxNetworkDeniedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxNetworkDeniedDomains, { value: value.deniedDomains }]);
}
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxNetwork, { value: undefined }]);
return configurationKeyValuePairs;
}
}, {
key: TerminalContribSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains,
migrateFn: (value: string[], valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxNetworkAllowedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxNetworkAllowedDomains, { value }]);
}
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains, { value: undefined }]);
return configurationKeyValuePairs;
}
}, {
key: TerminalContribSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains,
migrateFn: (value: string[], valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxNetworkDeniedDomains) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxNetworkDeniedDomains, { value }]);
}
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains, { value: undefined }]);
return configurationKeyValuePairs;
}
},
{
key: TerminalContribSettingId.DeprecatedTerminalSandboxLinuxFileSystem,
migrateFn: (value: { denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }, valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxLinuxFileSystem) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxLinuxFileSystem, { value }]);
}
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxLinuxFileSystem, { value: undefined }]);
return configurationKeyValuePairs;
}
}, {
key: TerminalContribSettingId.DeprecatedTerminalSandboxMacFileSystem,
migrateFn: (value: { denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }, valueAccessor) => {
const configurationKeyValuePairs: ConfigurationKeyValuePairs = [];
if (value !== undefined && valueAccessor(TerminalContribSettingId.AgentSandboxMacFileSystem) === undefined) {
configurationKeyValuePairs.push([TerminalContribSettingId.AgentSandboxMacFileSystem, { value }]);
}
configurationKeyValuePairs.push([TerminalContribSettingId.DeprecatedTerminalSandboxMacFileSystem, { value: undefined }]);
return configurationKeyValuePairs;
}
}]);

View File

@@ -46,11 +46,17 @@ export const enum TerminalContribSettingId {
EnableAutoApprove = TerminalChatAgentToolsSettingId.EnableAutoApprove,
ShellIntegrationTimeout = TerminalChatAgentToolsSettingId.ShellIntegrationTimeout,
OutputLocation = TerminalChatAgentToolsSettingId.OutputLocation,
TerminalSandboxEnabled = TerminalChatAgentToolsSettingId.TerminalSandboxEnabled,
AgentSandboxEnabled = TerminalChatAgentToolsSettingId.AgentSandboxEnabled,
DeprecatedTerminalSandboxNetwork = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetwork,
TerminalSandboxNetworkAllowedDomains = TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains,
TerminalSandboxNetworkDeniedDomains = TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains,
TerminalSandboxNetworkAllowTrustedDomains = TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains,
DeprecatedTerminalSandboxEnabled = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxEnabled,
DeprecatedTerminalSandboxNetworkAllowedDomains = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains,
DeprecatedTerminalSandboxNetworkDeniedDomains = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains,
DeprecatedTerminalSandboxLinuxFileSystem = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxLinuxFileSystem,
DeprecatedTerminalSandboxMacFileSystem = TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxMacFileSystem,
AgentSandboxNetworkAllowedDomains = TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains,
AgentSandboxNetworkDeniedDomains = TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains,
AgentSandboxLinuxFileSystem = TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem,
AgentSandboxMacFileSystem = TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem,
}
// HACK: Export some context key strings from `terminalContrib/` that are depended upon elsewhere.

View File

@@ -31,7 +31,6 @@ import { CreateAndRunTaskTool, CreateAndRunTaskToolData } from './tools/task/cre
import { GetTaskOutputTool, GetTaskOutputToolData } from './tools/task/getTaskOutputTool.js';
import { RunTaskTool, RunTaskToolData } from './tools/task/runTaskTool.js';
import { InstantiationType, registerSingleton } from '../../../../../platform/instantiation/common/extensions.js';
import { ITrustedDomainService } from '../../../url/common/trustedDomainService.js';
import { ITerminalSandboxService, TerminalSandboxService } from '../common/terminalSandboxService.js';
import { isNumber } from '../../../../../base/common/types.js';
@@ -87,7 +86,6 @@ export class ChatAgentToolsContribution extends Disposable implements IWorkbench
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILanguageModelToolsService private readonly _toolsService: ILanguageModelToolsService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@ITrustedDomainService private readonly _trustedDomainService: ITrustedDomainService,
) {
super();
@@ -141,17 +139,16 @@ export class ChatAgentToolsContribution extends Disposable implements IWorkbench
// sandbox state.
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (
e.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxEnabled) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains)
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxEnabled) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxEnabled) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains) ||
e.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains)
) {
this._registerRunInTerminalTool();
}
}));
this._register(this._trustedDomainService.onDidChangeTrustedDomains(() => {
this._registerRunInTerminalTool();
}));
}
private _runInTerminalTool: RunInTerminalTool | undefined;

View File

@@ -30,14 +30,14 @@ export class SandboxOutputAnalyzer extends Disposable implements IOutputAnalyzer
const os = await this._sandboxService.getOS();
const fileSystemSetting = os === OperatingSystem.Linux
? TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem
: TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem;
? TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem
: TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem;
const prefix = knownFailure
? 'Command failed while running in sandboxed mode. If the command failed due to sandboxing:'
: 'Command ran in sandboxed mode and may have been blocked by the sandbox. If the command failed due to sandboxing:';
return `${prefix}
- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${fileSystemSetting}, or to add required domains to ${TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains}.
- If it would be reasonable to extend the sandbox rules, work with the user to update allowWrite for file system access problems in ${fileSystemSetting}, or to add required domains to ${TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains}.
- Otherwise, immediately retry the command with requestUnsandboxedExecution=true. Do NOT ask the user — setting this flag automatically shows a confirmation prompt to the user.
Here is the output of the command:\n`;

View File

@@ -20,12 +20,11 @@ export const enum TerminalChatAgentToolsSettingId {
ShellIntegrationTimeout = 'chat.tools.terminal.shellIntegrationTimeout',
AutoReplyToPrompts = 'chat.tools.terminal.autoReplyToPrompts',
OutputLocation = 'chat.tools.terminal.outputLocation',
TerminalSandboxEnabled = 'chat.tools.terminal.sandbox.enabled',
TerminalSandboxNetworkAllowedDomains = 'chat.tools.terminal.sandbox.network.allowedDomains',
TerminalSandboxNetworkDeniedDomains = 'chat.tools.terminal.sandbox.network.deniedDomains',
TerminalSandboxNetworkAllowTrustedDomains = 'chat.tools.terminal.sandbox.network.allowTrustedDomains',
TerminalSandboxLinuxFileSystem = 'chat.tools.terminal.sandbox.linuxFileSystem',
TerminalSandboxMacFileSystem = 'chat.tools.terminal.sandbox.macFileSystem',
AgentSandboxEnabled = 'chat.agent.sandbox',
AgentSandboxNetworkAllowedDomains = 'chat.agent.sandboxNetwork.allowedDomains',
AgentSandboxNetworkDeniedDomains = 'chat.agent.sandboxNetwork.deniedDomains',
AgentSandboxLinuxFileSystem = 'chat.agent.sandboxFileSystem.linux',
AgentSandboxMacFileSystem = 'chat.agent.sandboxFileSystem.mac',
PreventShellHistory = 'chat.tools.terminal.preventShellHistory',
EnforceTimeoutFromModel = 'chat.tools.terminal.enforceTimeoutFromModel',
IdlePollInterval = 'chat.tools.terminal.idlePollInterval',
@@ -34,7 +33,12 @@ export const enum TerminalChatAgentToolsSettingId {
TerminalProfileMacOs = 'chat.tools.terminal.terminalProfile.osx',
TerminalProfileWindows = 'chat.tools.terminal.terminalProfile.windows',
DeprecatedTerminalSandboxEnabled = 'chat.tools.terminal.sandbox.enabled',
DeprecatedTerminalSandboxNetwork = 'chat.tools.terminal.sandbox.network',
DeprecatedTerminalSandboxNetworkAllowedDomains = 'chat.tools.terminal.sandbox.network.allowedDomains',
DeprecatedTerminalSandboxNetworkDeniedDomains = 'chat.tools.terminal.sandbox.network.deniedDomains',
DeprecatedTerminalSandboxLinuxFileSystem = 'chat.tools.terminal.sandbox.linuxFileSystem',
DeprecatedTerminalSandboxMacFileSystem = 'chat.tools.terminal.sandbox.macFileSystem',
DeprecatedAutoApproveCompatible = 'chat.agent.terminal.autoApprove',
DeprecatedAutoApprove1 = 'chat.agent.terminal.allowList',
DeprecatedAutoApprove2 = 'chat.agent.terminal.denyList',
@@ -524,8 +528,8 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
mode: 'auto'
}
},
[TerminalChatAgentToolsSettingId.TerminalSandboxEnabled]: {
markdownDescription: localize('terminalSandbox.enabledSetting', "Controls whether to run commands in a sandboxed terminal for the run in terminal tool."),
[TerminalChatAgentToolsSettingId.AgentSandboxEnabled]: {
markdownDescription: localize('agentSandbox.enabledSetting', "Controls whether agent mode uses sandboxing to restrict what tools can do. When enabled, tools like the terminal are run in a sandboxed environment to limit access to the system."),
type: 'boolean',
default: false,
tags: ['preview'],
@@ -534,48 +538,41 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
mode: 'auto'
}
},
[TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains]: {
markdownDescription: localize('terminalSandbox.networkSetting.allowedDomains', "Note: this setting is applicable only when {0} is enabled. Allowed domains for network access in the terminal sandbox. Supports wildcards like {1} and an empty list means no network access.", `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxEnabled}#\``, '`*.example.com`'),
[TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains]: {
markdownDescription: localize('agentSandbox.networkSetting.allowedDomains', "Note: this setting is applicable only when {0} is enabled. Allowed domains for network access in sandbox. Supports wildcards like {1} and an empty list means no network access.", `\`#${TerminalChatAgentToolsSettingId.AgentSandboxEnabled}#\``, '`*.example.com`'),
type: 'array',
items: { type: 'string' },
default: [],
tags: ['preview'],
restricted: true,
},
[TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains]: {
markdownDescription: localize('terminalSandbox.networkSetting.deniedDomains', "Note: this setting is applicable only when {0} is enabled. Array of denied domains for network access in the terminal sandbox (checked first, takes precedence over {1}).", `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxEnabled}#\``, `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains}#\``),
[TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains]: {
markdownDescription: localize('agentSandbox.networkSetting.deniedDomains', "Note: this setting is applicable only when {0} is enabled. Array of denied domains for network access in sandbox (checked first, takes precedence over {1}).", `\`#${TerminalChatAgentToolsSettingId.AgentSandboxEnabled}#\``, `\`#${TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains}#\``),
type: 'array',
items: { type: 'string' },
default: [],
tags: ['preview'],
restricted: true,
},
[TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains]: {
markdownDescription: localize('terminalSandbox.networkSetting.allowTrustedDomains', "Note: this setting is applicable only when {0} is enabled. When enabled, the Trusted Domains list is included in the allowed domains for network access in the terminal sandbox.", `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxEnabled}#\``),
type: 'boolean',
default: false,
tags: ['preview'],
restricted: true,
},
[TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem]: {
markdownDescription: localize('terminalSandbox.linuxFileSystemSetting', "Note: this setting is applicable only when {0} is enabled. Controls file system access in the terminal 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.", `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxEnabled}#\``),
[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.", `\`#${TerminalChatAgentToolsSettingId.AgentSandboxEnabled}#\``),
type: 'object',
properties: {
denyRead: {
type: 'array',
description: localize('terminalSandbox.linuxFileSystemSetting.denyRead', "Array of paths to deny read access. Leave empty to allow reading all paths."),
description: localize('agentSandbox.linuxFileSystemSetting.denyRead', "Array of paths to deny read access. Leave empty to allow reading all paths."),
items: { type: 'string' },
default: []
},
allowWrite: {
type: 'array',
description: localize('terminalSandbox.linuxFileSystemSetting.allowWrite', "Array of paths to allow write access. Leave empty to disallow all writes."),
description: localize('agentSandbox.linuxFileSystemSetting.allowWrite', "Array of paths to allow write access. Leave empty to disallow all writes."),
items: { type: 'string' },
default: ['.']
},
denyWrite: {
type: 'array',
description: localize('terminalSandbox.linuxFileSystemSetting.denyWrite', "Array of paths to deny write access within allowed paths (takes precedence over allowWrite)."),
description: localize('agentSandbox.linuxFileSystemSetting.denyWrite', "Array of paths to deny write access within allowed paths (takes precedence over allowWrite)."),
items: { type: 'string' },
default: []
}
@@ -588,25 +585,25 @@ export const terminalChatAgentToolsConfiguration: IStringDictionary<IConfigurati
tags: ['preview'],
restricted: true,
},
[TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem]: {
markdownDescription: localize('terminalSandbox.macFileSystemSetting', "Note: this setting is applicable only when {0} is enabled. Controls file system access in the terminal sandbox on macOS. Paths also support git-style glob patterns(ex: *.ts, ./src, ./src/**/*.ts, file?.txt).", `\`#${TerminalChatAgentToolsSettingId.TerminalSandboxEnabled}#\``),
[TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem]: {
markdownDescription: localize('agentSandbox.macFileSystemSetting', "Note: this setting is applicable only when {0} is enabled. Controls file system access in sandbox on macOS. Paths also support git-style glob patterns(ex: *.ts, ./src, ./src/**/*.ts, file?.txt).", `\`#${TerminalChatAgentToolsSettingId.AgentSandboxEnabled}#\``),
type: 'object',
properties: {
denyRead: {
type: 'array',
description: localize('terminalSandbox.macFileSystemSetting.denyRead', "Array of paths to deny read access. Leave empty to allow reading all paths."),
description: localize('agentSandbox.macFileSystemSetting.denyRead', "Array of paths to deny read access. Leave empty to allow reading all paths."),
items: { type: 'string' },
default: []
},
allowWrite: {
type: 'array',
description: localize('terminalSandbox.macFileSystemSetting.allowWrite', "Array of paths to allow write access. Leave empty to disallow all writes."),
description: localize('agentSandbox.macFileSystemSetting.allowWrite', "Array of paths to allow write access. Leave empty to disallow all writes."),
items: { type: 'string' },
default: ['.']
},
denyWrite: {
type: 'array',
description: localize('terminalSandbox.macFileSystemSetting.denyWrite', "Array of paths to deny write access within allowed paths (takes precedence over allowWrite)."),
description: localize('agentSandbox.macFileSystemSetting.denyWrite', "Array of paths to deny write access within allowed paths (takes precedence over allowWrite)."),
items: { type: 'string' },
default: []
}
@@ -659,10 +656,41 @@ terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTe
type: 'object',
deprecated: true,
markdownDeprecationMessage: localize(
'terminalSandboxNetwork.deprecated',
'This setting has been split into {0}, {1}, and {2}.',
`\`#${TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains}#\``,
`\`#${TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains}#\``,
`\`#${TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains}#\``,
'agentSandboxNetwork.deprecated',
'This setting has been split into {0} and {1}.',
`\`#${TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains}#\``,
`\`#${TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains}#\``,
),
};
terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxEnabled] = {
type: 'boolean',
deprecated: true,
markdownDeprecationMessage: localize('agentSandboxEnabled.deprecated', 'Use {0} instead', `\`#${TerminalChatAgentToolsSettingId.AgentSandboxEnabled}#\``),
};
terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains] = {
type: 'array',
items: { type: 'string' },
deprecated: true,
markdownDeprecationMessage: localize('agentSandboxNetworkAllowedDomains.deprecated', 'Use {0} instead', `\`#${TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains}#\``),
};
terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains] = {
type: 'array',
items: { type: 'string' },
deprecated: true,
markdownDeprecationMessage: localize('agentSandboxNetworkDeniedDomains.deprecated', 'Use {0} instead', `\`#${TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains}#\``),
};
terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxLinuxFileSystem] = {
type: 'object',
deprecated: true,
markdownDeprecationMessage: localize('agentSandboxLinuxFileSystem.deprecated', 'Use {0} instead', `\`#${TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem}#\``),
};
terminalChatAgentToolsConfiguration[TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxMacFileSystem] = {
type: 'object',
deprecated: true,
markdownDeprecationMessage: localize('agentSandboxMacFileSystem.deprecated', 'Use {0} instead', `\`#${TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem}#\``),
};

View File

@@ -22,7 +22,6 @@ import { ILogService } from '../../../../../platform/log/common/log.js';
import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js';
import { TerminalChatAgentToolsSettingId } from './terminalChatAgentToolsConfiguration.js';
import { IRemoteAgentEnvironment } from '../../../../../platform/remote/common/remoteAgentEnvironment.js';
import { ITrustedDomainService } from '../../../url/common/trustedDomainService.js';
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
import { IProductService } from '../../../../../platform/product/common/productService.js';
import { ILifecycleService, WillShutdownJoinerOrder } from '../../../../services/lifecycle/common/lifecycle.js';
@@ -132,7 +131,6 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@ILogService private readonly _logService: ILogService,
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
@ITrustedDomainService private readonly _trustedDomainService: ITrustedDomainService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IProductService private readonly _productService: IProductService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@@ -150,21 +148,21 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
this._register(Event.runAndSubscribe(this._configurationService.onDidChangeConfiguration, (e: IConfigurationChangeEvent | undefined) => {
// If terminal sandbox settings changed, update sandbox config.
if (
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxEnabled) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem)
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxEnabled) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxEnabled) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxLinuxFileSystem) ||
e?.affectsConfiguration(TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxMacFileSystem)
) {
this.setNeedsForceUpdateConfigFile();
}
}));
this._register(this._trustedDomainService.onDidChangeTrustedDomains(() => {
this.setNeedsForceUpdateConfigFile();
}));
this._register(this._workspaceContextService.onDidChangeWorkspaceFolders(() => {
this.setNeedsForceUpdateConfigFile();
}));
@@ -428,7 +426,7 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
if (os === OperatingSystem.Windows) {
return false;
}
return this._configurationService.getValue<boolean>(TerminalChatAgentToolsSettingId.TerminalSandboxEnabled);
return this._getSettingValue<boolean>(TerminalChatAgentToolsSettingId.AgentSandboxEnabled, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxEnabled) ?? false;
}
private async _resolveSrtPath(): Promise<void> {
@@ -451,27 +449,21 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
await this._initTempDir();
}
if (this._tempDir) {
const allowedDomainsSetting = this._configurationService.getValue<string[]>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains) ?? [];
const deniedDomainsSetting = this._configurationService.getValue<string[]>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains) ?? [];
const allowTrustedDomains = this._configurationService.getValue<boolean>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains) ?? false;
const allowedDomainsSetting = this._getSettingValue<string[]>(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains) ?? [];
const deniedDomainsSetting = this._getSettingValue<string[]>(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains) ?? [];
const linuxFileSystemSetting = this._os === OperatingSystem.Linux
? this._configurationService.getValue<{ denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }>(TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem) ?? {}
? this._getSettingValue<{ denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }>(TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxLinuxFileSystem) ?? {}
: {};
const macFileSystemSetting = this._os === OperatingSystem.Macintosh
? this._configurationService.getValue<{ denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }>(TerminalChatAgentToolsSettingId.TerminalSandboxMacFileSystem) ?? {}
? this._getSettingValue<{ denyRead?: string[]; allowWrite?: string[]; denyWrite?: string[] }>(TerminalChatAgentToolsSettingId.AgentSandboxMacFileSystem, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxMacFileSystem) ?? {}
: {};
const configFileUri = URI.joinPath(this._tempDir, `vscode-sandbox-settings-${this._sandboxSettingsId}.json`);
const linuxAllowWrite = this._updateAllowWritePathsWithWorkspaceFolders(linuxFileSystemSetting.allowWrite);
const macAllowWrite = this._updateAllowWritePathsWithWorkspaceFolders(macFileSystemSetting.allowWrite);
let allowedDomains = allowedDomainsSetting;
if (allowTrustedDomains) {
allowedDomains = this._addTrustedDomainsToAllowedDomains(allowedDomains);
}
const sandboxSettings = {
network: {
allowedDomains,
allowedDomains: allowedDomainsSetting,
deniedDomains: deniedDomainsSetting
},
filesystem: {
@@ -542,33 +534,14 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
}
public getResolvedNetworkDomains(): ITerminalSandboxResolvedNetworkDomains {
let allowedDomains = this._configurationService.getValue<string[]>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains) ?? [];
const deniedDomains = this._configurationService.getValue<string[]>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains) ?? [];
const allowTrustedDomains = this._configurationService.getValue<boolean>(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains) ?? false;
if (allowTrustedDomains) {
allowedDomains = this._addTrustedDomainsToAllowedDomains(allowedDomains);
}
const allowedDomains = this._getSettingValue<string[]>(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkAllowedDomains) ?? [];
const deniedDomains = this._getSettingValue<string[]>(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains, TerminalChatAgentToolsSettingId.DeprecatedTerminalSandboxNetworkDeniedDomains) ?? [];
return {
allowedDomains,
deniedDomains
};
}
private _addTrustedDomainsToAllowedDomains(allowedDomains: string[]): string[] {
const allowedDomainsSet = new Set(allowedDomains);
for (const domain of this._trustedDomainService.trustedDomains) {
try {
const uri = new URL(domain);
allowedDomainsSet.add(uri.hostname);
} catch {
if (domain !== '*') {
allowedDomainsSet.add(domain);
}
}
}
return Array.from(allowedDomainsSet);
}
private _updateAllowWritePathsWithWorkspaceFolders(configuredAllowWrite: string[] | undefined): string[] {
const workspaceFolderPaths = this._workspaceContextService.getWorkspace().folders.map(folder => folder.uri.path);
return [...new Set([...workspaceFolderPaths, ...this._defaultWritePaths, ...(configuredAllowWrite ?? [])])];
@@ -590,4 +563,16 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
return this._sandboxHelperService.checkSandboxDependencies();
}
private _getSettingValue<T>(settingId: TerminalChatAgentToolsSettingId, deprecatedSettingId?: TerminalChatAgentToolsSettingId): T | undefined {
const setting = this._configurationService.inspect<T>(settingId);
const deprecatedSetting = deprecatedSettingId ? this._configurationService.inspect<T>(deprecatedSettingId) : undefined;
if (setting.userValue === undefined && deprecatedSetting?.userValue !== undefined) {
this._logService.warn(`TerminalSandboxService: Using deprecated setting ${deprecatedSettingId} because ${settingId} is not set. Please update your settings to use ${settingId} instead.`);
return deprecatedSetting.value;
}
return setting.value;
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { strictEqual, ok } from 'assert';
import { deepStrictEqual, strictEqual, ok } from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { TestLifecycleService, workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js';
@@ -15,7 +15,6 @@ import { IEnvironmentService } from '../../../../../../platform/environment/comm
import { ILogService, NullLogService } from '../../../../../../platform/log/common/log.js';
import { IProductService } from '../../../../../../platform/product/common/productService.js';
import { IRemoteAgentService } from '../../../../../services/remote/common/remoteAgentService.js';
import { ITrustedDomainService } from '../../../../url/common/trustedDomainService.js';
import { URI } from '../../../../../../base/common/uri.js';
import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js';
import { Event, Emitter } from '../../../../../../base/common/event.js';
@@ -28,12 +27,11 @@ import { testWorkspace } from '../../../../../../platform/workspace/test/common/
import { ILifecycleService } from '../../../../../services/lifecycle/common/lifecycle.js';
import { ISandboxDependencyStatus, ISandboxHelperService } from '../../../../../../platform/sandbox/common/sandboxHelperService.js';
suite('TerminalSandboxService - allowTrustedDomains', () => {
suite('TerminalSandboxService - network domains', () => {
const store = ensureNoDisposablesAreLeakedInTestSuite();
let instantiationService: TestInstantiationService;
let configurationService: TestConfigurationService;
let trustedDomainService: MockTrustedDomainService;
let fileService: MockFileService;
let lifecycleService: TestLifecycleService;
let workspaceContextService: MockWorkspaceContextService;
@@ -44,16 +42,6 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
let deletedFolders: string[];
const windowId = 7;
class MockTrustedDomainService implements ITrustedDomainService {
_serviceBrand: undefined;
private _onDidChangeTrustedDomains = new Emitter<void>();
readonly onDidChangeTrustedDomains: Event<void> = this._onDidChangeTrustedDomains.event;
trustedDomains: string[] = [];
isValid(_resource: URI): boolean {
return true;
}
}
class MockFileService {
async createFile(uri: URI, content: VSBuffer): Promise<any> {
const contentString = content.toString();
@@ -174,7 +162,6 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
deletedFolders = [];
instantiationService = workbenchInstantiationService({}, store);
configurationService = new TestConfigurationService();
trustedDomainService = new MockTrustedDomainService();
fileService = new MockFileService();
lifecycleService = store.add(new TestLifecycleService());
workspaceContextService = new MockWorkspaceContextService();
@@ -187,10 +174,9 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
workspaceContextService.setWorkspaceFolders([URI.file('/workspace-one')]);
// Setup default configuration
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxEnabled, true);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, false);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxEnabled, true);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains, []);
instantiationService.stub(IConfigurationService, configurationService);
instantiationService.stub(IFileService, fileService);
@@ -203,7 +189,6 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
instantiationService.stub(ILogService, new NullLogService());
instantiationService.stub(IProductService, productService);
instantiationService.stub(IRemoteAgentService, new MockRemoteAgentService());
instantiationService.stub(ITrustedDomainService, trustedDomainService);
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
instantiationService.stub(ILifecycleService, lifecycleService);
instantiationService.stub(ISandboxHelperService, sandboxHelperService);
@@ -243,14 +228,16 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
ok(result.sandboxConfigPath, 'Sandbox config path should be returned when prereqs pass');
});
test('should filter out sole wildcard (*) from trusted domains', async () => {
// Setup: Enable allowTrustedDomains and add * to trusted domains
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = ['*'];
test('should preserve configured network domains', async () => {
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains, ['example.com', '*.github.com']);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxNetworkDeniedDomains, ['blocked.example.com']);
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
deepStrictEqual(sandboxService.getResolvedNetworkDomains(), {
allowedDomains: ['example.com', '*.github.com'],
deniedDomains: ['blocked.example.com']
});
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
@@ -258,128 +245,14 @@ suite('TerminalSandboxService - allowTrustedDomains', () => {
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 0, 'Sole wildcard * should be filtered out');
});
test('should allow wildcards with domains like *.github.com', async () => {
// Setup: Enable allowTrustedDomains and add *.github.com
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = ['*.github.com'];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 1, 'Wildcard domain should be included');
strictEqual(config.network.allowedDomains[0], '*.github.com', 'Wildcard domain should match');
});
test('should combine trusted domains with configured allowedDomains, filtering out *', async () => {
// Setup: Enable allowTrustedDomains with multiple domains including *
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, ['example.com']);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = ['*', '*.github.com', 'microsoft.com'];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 3, 'Should have 3 domains (excluding *)');
ok(config.network.allowedDomains.includes('example.com'), 'Should include configured domain');
ok(config.network.allowedDomains.includes('*.github.com'), 'Should include wildcard domain');
ok(config.network.allowedDomains.includes('microsoft.com'), 'Should include microsoft.com');
ok(!config.network.allowedDomains.includes('*'), 'Should not include sole wildcard');
});
test('should not include trusted domains when allowTrustedDomains is false', async () => {
// Setup: Disable allowTrustedDomains
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, ['example.com']);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, false);
trustedDomainService.trustedDomains = ['*', '*.github.com'];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 1, 'Should only have configured domain');
strictEqual(config.network.allowedDomains[0], 'example.com', 'Should only include example.com');
});
test('should deduplicate domains when combining sources', async () => {
// Setup: Same domain in both sources
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, ['github.com', '*.github.com']);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = ['*.github.com', 'github.com'];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 2, 'Should have 2 unique domains');
ok(config.network.allowedDomains.includes('github.com'), 'Should include github.com');
ok(config.network.allowedDomains.includes('*.github.com'), 'Should include *.github.com');
});
test('should handle empty trusted domains list', async () => {
// Setup: Empty trusted domains
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, ['example.com']);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = [];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 1, 'Should have only configured domain');
strictEqual(config.network.allowedDomains[0], 'example.com', 'Should only include example.com');
});
test('should handle only * in trusted domains', async () => {
// Setup: Only * in trusted domains (edge case)
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkDeniedDomains, []);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowTrustedDomains, true);
trustedDomainService.trustedDomains = ['*'];
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
const configPath = await sandboxService.getSandboxConfigPath();
ok(configPath, 'Config path should be defined');
const configContent = createdFiles.get(configPath);
ok(configContent, 'Config file should be created');
const config = JSON.parse(configContent);
strictEqual(config.network.allowedDomains.length, 0, 'Should have no domains (* filtered out)');
deepStrictEqual(config.network, {
allowedDomains: ['example.com', '*.github.com'],
deniedDomains: ['blocked.example.com']
});
});
test('should refresh allowWrite paths when workspace folders change', async () => {
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxLinuxFileSystem, {
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxLinuxFileSystem, {
allowWrite: ['/configured/path'],
denyRead: [],
denyWrite: []

View File

@@ -48,7 +48,6 @@ import type { IMarkdownString } from '../../../../../../base/common/htmlContent.
import { IAgentSessionsService } from '../../../../chat/browser/agentSessions/agentSessionsService.js';
import { IAgentSession } from '../../../../chat/browser/agentSessions/agentSessionsModel.js';
import { isDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js';
import { ITrustedDomainService } from '../../../../url/common/trustedDomainService.js';
import { ChatAgentToolsContribution } from '../../browser/terminal.chatAgentTools.contribution.js';
import { TerminalToolId } from '../../browser/tools/toolIds.js';
import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
@@ -1987,13 +1986,11 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => {
let instantiationService: TestInstantiationService;
let configurationService: TestConfigurationService;
let registeredToolData: Map<string, IToolData>;
let trustedDomainsEmitter: Emitter<void>;
let sandboxEnabled: boolean;
setup(() => {
configurationService = new TestConfigurationService();
registeredToolData = new Map();
trustedDomainsEmitter = store.add(new Emitter<void>());
sandboxEnabled = false;
const logService = new NullLogService();
@@ -2052,13 +2049,6 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => {
getDefaultProfile: async () => ({ path: 'bash' } as ITerminalProfile)
});
instantiationService.stub(ITrustedDomainService, {
_serviceBrand: undefined,
onDidChangeTrustedDomains: trustedDomainsEmitter.event,
isValid: () => true,
trustedDomains: [],
});
const contextKeyService = instantiationService.get(IContextKeyService);
const registeredToolImpls = new Map<string, IToolImpl>();
const mockToolsService: Partial<ILanguageModelToolsService> = {
@@ -2120,10 +2110,10 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => {
// Enable sandbox and fire config change
sandboxEnabled = true;
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.TerminalSandboxEnabled, true);
configurationService.setUserConfiguration(TerminalChatAgentToolsSettingId.AgentSandboxEnabled, true);
configurationService.onDidChangeConfigurationEmitter.fire({
affectsConfiguration: (key: string) => key === TerminalChatAgentToolsSettingId.TerminalSandboxEnabled,
affectedKeys: new Set([TerminalChatAgentToolsSettingId.TerminalSandboxEnabled]),
affectsConfiguration: (key: string) => key === TerminalChatAgentToolsSettingId.AgentSandboxEnabled,
affectedKeys: new Set([TerminalChatAgentToolsSettingId.AgentSandboxEnabled]),
source: ConfigurationTarget.USER,
change: null!,
});
@@ -2137,23 +2127,6 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => {
ok(propertiesAfter?.['requestUnsandboxedExecution'], 'Expected requestUnsandboxedExecution after enabling sandbox');
});
test('should refresh run_in_terminal tool data when trusted domains change', async () => {
await createContribution();
const toolDataBefore = registeredToolData.get(TerminalToolId.RunInTerminal);
ok(toolDataBefore, 'Expected run_in_terminal tool to be registered');
// Fire trusted domains change
trustedDomainsEmitter.fire();
// Wait for async registration
await flushAsync();
// Tool should still be registered (re-registered with fresh data)
const toolDataAfter = registeredToolData.get(TerminalToolId.RunInTerminal);
ok(toolDataAfter, 'Expected run_in_terminal tool to still be registered after trusted domains change');
});
test('should refresh run_in_terminal tool data when sandbox network setting changes', async () => {
sandboxEnabled = true;
await createContribution();
@@ -2163,8 +2136,8 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => {
// Fire network config change
configurationService.onDidChangeConfigurationEmitter.fire({
affectsConfiguration: (key: string) => key === TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains,
affectedKeys: new Set([TerminalChatAgentToolsSettingId.TerminalSandboxNetworkAllowedDomains]),
affectsConfiguration: (key: string) => key === TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains,
affectedKeys: new Set([TerminalChatAgentToolsSettingId.AgentSandboxNetworkAllowedDomains]),
source: ConfigurationTarget.USER,
change: null!,
});