Skip to content

Commit b3c7655

Browse files
authored
Merge pull request microsoft#259776 from microsoft/tyriar/259760
Warn about web requests in run in terminal
2 parents fb5d471 + 1c43b8d commit b3c7655

File tree

3 files changed

+52
-5
lines changed

3 files changed

+52
-5
lines changed

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolSubPart.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import * as dom from '../../../../../../base/browser/dom.js';
7-
import { MarkdownString } from '../../../../../../base/common/htmlContent.js';
7+
import { MarkdownString, type IMarkdownString } from '../../../../../../base/common/htmlContent.js';
88
import { thenIfNotDisposed } from '../../../../../../base/common/lifecycle.js';
99
import { Schemas } from '../../../../../../base/common/network.js';
1010
import { URI } from '../../../../../../base/common/uri.js';
@@ -18,13 +18,14 @@ import { IInstantiationService } from '../../../../../../platform/instantiation/
1818
import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js';
1919
import { ChatContextKeys } from '../../../common/chatContextKeys.js';
2020
import { IChatToolInvocation, type IChatTerminalToolInvocationData } from '../../../common/chatService.js';
21+
import type { CodeBlockModelCollection } from '../../../common/codeBlockModelCollection.js';
2122
import { CancelChatActionId } from '../../actions/chatExecuteActions.js';
2223
import { AcceptToolConfirmationActionId } from '../../actions/chatToolActions.js';
2324
import { IChatCodeBlockInfo, IChatWidgetService } from '../../chat.js';
2425
import { ICodeBlockRenderOptions } from '../../codeBlockPart.js';
2526
import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatConfirmationWidget.js';
2627
import { IChatContentPartRenderContext } from '../chatContentParts.js';
27-
import { EditorPool } from '../chatMarkdownContentPart.js';
28+
import { ChatMarkdownContentPart, EditorPool } from '../chatMarkdownContentPart.js';
2829
import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js';
2930

3031
export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSubPart {
@@ -38,6 +39,7 @@ export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSub
3839
private readonly renderer: MarkdownRenderer,
3940
private readonly editorPool: EditorPool,
4041
private readonly currentWidthDelegate: () => number,
42+
private readonly codeBlockModelCollection: CodeBlockModelCollection,
4143
private readonly codeBlockStartIndex: number,
4244
@IInstantiationService private readonly instantiationService: IInstantiationService,
4345
@IKeybindingService keybindingService: IKeybindingService,
@@ -52,8 +54,7 @@ export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSub
5254
throw new Error('Confirmation messages are missing');
5355
}
5456

55-
const title = toolInvocation.confirmationMessages.title;
56-
const message = toolInvocation.confirmationMessages.message;
57+
const { title, message, disclaimer } = toolInvocation.confirmationMessages;
5758
const continueLabel = localize('continue', "Continue");
5859
const continueKeybinding = keybindingService.lookupKeybinding(AcceptToolConfirmationActionId)?.getLabel();
5960
const continueTooltip = continueKeybinding ? `${continueLabel} (${continueKeybinding})` : continueLabel;
@@ -136,6 +137,10 @@ export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSub
136137
this.context.container,
137138
));
138139

140+
if (disclaimer) {
141+
this._appendMarkdownPart(element, disclaimer, codeBlockRenderOptions);
142+
}
143+
139144
ChatContextKeys.Editing.hasToolConfirmation.bindTo(this.contextKeyService).set(true);
140145
this._register(confirmWidget.onDidClick(button => {
141146
toolInvocation.confirmed.complete(button.data);
@@ -155,4 +160,22 @@ export class TerminalConfirmationWidgetSubPart extends BaseChatToolInvocationSub
155160
path: generateUuid(),
156161
});
157162
}
163+
164+
private _appendMarkdownPart(container: HTMLElement, message: string | IMarkdownString, codeBlockRenderOptions: ICodeBlockRenderOptions) {
165+
const part = this._register(this.instantiationService.createInstance(ChatMarkdownContentPart, {
166+
kind: 'markdownContent',
167+
content: typeof message === 'string' ? new MarkdownString().appendText(message) : message
168+
},
169+
this.context,
170+
this.editorPool,
171+
false,
172+
this.codeBlockStartIndex,
173+
this.renderer,
174+
this.currentWidthDelegate(),
175+
this.codeBlockModelCollection,
176+
{ codeBlockRenderOptions }
177+
));
178+
dom.append(container, part.domNode);
179+
this._register(part.onDidChangeHeight(() => this._onDidChangeHeight.fire()));
180+
}
158181
}

src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatToolInvocationPart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export class ChatToolInvocationPart extends Disposable implements IChatContentPa
8686
}
8787
if (this.toolInvocation.confirmationMessages) {
8888
if (this.toolInvocation.toolSpecificData?.kind === 'terminal') {
89-
return this.instantiationService.createInstance(TerminalConfirmationWidgetSubPart, this.toolInvocation, this.toolInvocation.toolSpecificData, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockStartIndex);
89+
return this.instantiationService.createInstance(TerminalConfirmationWidgetSubPart, this.toolInvocation, this.toolInvocation.toolSpecificData, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockModelCollection, this.codeBlockStartIndex);
9090
} else {
9191
return this.instantiationService.createInstance(ToolConfirmationSubPart, this.toolInvocation, this.context, this.renderer, this.editorPool, this.currentWidthDelegate, this.codeBlockModelCollection, this.codeBlockStartIndex);
9292
}

src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalTool.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { RichExecuteStrategy } from './executeStrategy/richExecuteStrategy.js';
3737
import { isPowerShell } from './runInTerminalHelpers.js';
3838
import { extractInlineSubCommands, splitCommandLineIntoSubCommands } from './subCommands.js';
3939
import { ShellIntegrationQuality, ToolTerminalCreator, type IToolTerminal } from './toolTerminalCreator.js';
40+
import { Codicon } from '../../../../../base/common/codicons.js';
4041

4142
const TERMINAL_SESSION_STORAGE_KEY = 'chat.terminalSessions';
4243

@@ -118,6 +119,17 @@ const telemetryIgnoredSequences = [
118119
'\x1b[O', // Focus out
119120
];
120121

122+
const promptInjectionWarningCommandsLower = [
123+
'curl',
124+
'wget',
125+
];
126+
const promptInjectionWarningCommandsLowerPwshOnly = [
127+
'invoke-restmethod',
128+
'invoke-webrequest',
129+
'irm',
130+
'iwr',
131+
];
132+
121133
export class RunInTerminalTool extends Disposable implements IToolImpl {
122134

123135
protected readonly _commandLineAutoApprover: CommandLineAutoApprover;
@@ -228,11 +240,23 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
228240
this._logService.info(`- ${reason}`);
229241
}
230242

243+
// Add a disclaimer warning about prompt injection for common commands that return
244+
// content from the web
245+
let disclaimer: IMarkdownString | undefined;
246+
const subCommandsLowerFirstWordOnly = subCommands.map(command => command.split(' ')[0].toLowerCase());
247+
if (!isAutoApproved && (
248+
subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLower.includes(command)) ||
249+
(isPowerShell(shell, os) && subCommandsLowerFirstWordOnly.some(command => promptInjectionWarningCommandsLowerPwshOnly.includes(command)))
250+
)) {
251+
disclaimer = new MarkdownString(`$(${Codicon.info.id}) ` + localize('runInTerminal.promptInjectionDisclaimer', 'Web content may contain malicious code or attempt prompt injection attacks.'), { supportThemeIcons: true });
252+
}
253+
231254
confirmationMessages = isAutoApproved ? undefined : {
232255
title: args.isBackground
233256
? localize('runInTerminal.background', "Run command in background terminal")
234257
: localize('runInTerminal.foreground', "Run command in terminal"),
235258
message: new MarkdownString(args.explanation),
259+
disclaimer,
236260
};
237261
}
238262

0 commit comments

Comments
 (0)