Skip to content

Commit 0a11ecd

Browse files
authored
terminal: reduce sandbox URL false positives (#308894)
Restrict bare host detection to a conservative set of coding-related domain suffixes while continuing to treat explicit URLs and SSH remotes as domains. Add regression coverage for valid and invalid bare suffixes, multi-label hosts, explicit URLs, and dotted non-domain identifiers.
1 parent b38be3c commit 0a11ecd

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

src/vs/workbench/contrib/terminalContrib/chatAgentTools/common/terminalSandboxService.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
142142
'py', 'rar', 'rs', 'so', 'sql', 'svg', 'tar', 'tgz', 'toml', 'ts', 'tsx', 'txt', 'wasm', 'webp',
143143
'xml', 'yaml', 'yml', 'zip'
144144
]);
145+
// Bare host detection is a heuristic used only to prompt before running commands that appear to access the network.
146+
// Keep this list intentionally conservative and focused on TLDs commonly used for coding-related hosts and services.
147+
private static readonly _wellKnownDomainSuffixes = new Set([
148+
'ai', 'cloud', 'com', 'dev', 'io', 'me', 'net', 'org', 'tech'
149+
]);
145150

146151
constructor(
147152
@IConfigurationService private readonly _configurationService: IConfigurationService,
@@ -581,6 +586,9 @@ export class TerminalSandboxService extends Disposable implements ITerminalSandb
581586
if (TerminalSandboxService._fileExtensionSuffixes.has(lastLabel)) {
582587
return undefined;
583588
}
589+
if (!TerminalSandboxService._wellKnownDomainSuffixes.has(lastLabel)) {
590+
return undefined;
591+
}
584592
}
585593

586594
return hasWildcardPrefix ? `*.${host}` : host;

src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/browser/terminalSandboxService.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,36 @@ suite('TerminalSandboxService - network domains', () => {
427427
strictEqual(jsonResult.blockedDomains, undefined, 'File extensions such as .json should not be reported as domains');
428428
});
429429

430+
test('should ignore bare dotted values with unknown domain suffixes', async () => {
431+
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
432+
await sandboxService.getSandboxConfigPath();
433+
434+
const commands = [
435+
'echo test.invalidtld',
436+
'echo test.org.invalidtld',
437+
'echo session.completed',
438+
];
439+
440+
for (const command of commands) {
441+
const wrapResult = sandboxService.wrapCommand(command, false, 'bash');
442+
strictEqual(wrapResult.isSandboxWrapped, true, `Command ${command} should remain sandboxed`);
443+
strictEqual(wrapResult.blockedDomains, undefined, `Command ${command} should not report a blocked domain`);
444+
}
445+
});
446+
447+
test('should still detect bare hosts with well-known domain suffixes', async () => {
448+
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
449+
await sandboxService.getSandboxConfigPath();
450+
451+
const testComResult = sandboxService.wrapCommand('curl test.com', false, 'bash');
452+
strictEqual(testComResult.isSandboxWrapped, false, 'Well-known bare domain suffixes should trigger domain checks');
453+
deepStrictEqual(testComResult.blockedDomains, ['test.com']);
454+
455+
const testOrgComResult = sandboxService.wrapCommand('curl test.org.com', false, 'bash');
456+
strictEqual(testOrgComResult.isSandboxWrapped, false, 'Well-known bare domain suffixes should trigger domain checks for multi-label hosts');
457+
deepStrictEqual(testOrgComResult.blockedDomains, ['test.org.com']);
458+
});
459+
430460
test('should still treat URL authorities with file-like suffixes as domains', async () => {
431461
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
432462
await sandboxService.getSandboxConfigPath();
@@ -437,6 +467,16 @@ suite('TerminalSandboxService - network domains', () => {
437467
deepStrictEqual(wrapResult.blockedDomains, ['example.zip']);
438468
});
439469

470+
test('should still treat URL authorities with unknown suffixes as domains', async () => {
471+
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
472+
await sandboxService.getSandboxConfigPath();
473+
474+
const wrapResult = sandboxService.wrapCommand('curl https://example.bar/path', false, 'bash');
475+
476+
strictEqual(wrapResult.isSandboxWrapped, false, 'URL authorities should not require a well-known bare-host suffix');
477+
deepStrictEqual(wrapResult.blockedDomains, ['example.bar']);
478+
});
479+
440480
test('should still treat ssh remotes with file-like suffixes as domains', async () => {
441481
const sandboxService = store.add(instantiationService.createInstance(TerminalSandboxService));
442482
await sandboxService.getSandboxConfigPath();

0 commit comments

Comments
 (0)