mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-20 02:08:47 +00:00
Ensure session rules only apply to a single session
This commit is contained in:
@@ -266,8 +266,9 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
|
|||||||
const userRules = newRules.filter(r => r.scope === 'user');
|
const userRules = newRules.filter(r => r.scope === 'user');
|
||||||
|
|
||||||
// Handle session-scoped rules (temporary, in-memory only)
|
// Handle session-scoped rules (temporary, in-memory only)
|
||||||
|
const chatSessionId = this.context.element.sessionId;
|
||||||
for (const rule of sessionRules) {
|
for (const rule of sessionRules) {
|
||||||
this.terminalChatService.addSessionAutoApproveRule(rule.key, rule.value);
|
this.terminalChatService.addSessionAutoApproveRule(chatSessionId, rule.key, rule.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle workspace-scoped rules
|
// Handle workspace-scoped rules
|
||||||
|
|||||||
@@ -220,16 +220,18 @@ export interface ITerminalChatService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a session-scoped auto-approve rule.
|
* Add a session-scoped auto-approve rule.
|
||||||
|
* @param chatSessionId The chat session ID to associate the rule with
|
||||||
* @param key The rule key (command or regex pattern)
|
* @param key The rule key (command or regex pattern)
|
||||||
* @param value The rule value (approval boolean or object with approve and matchCommandLine)
|
* @param value The rule value (approval boolean or object with approve and matchCommandLine)
|
||||||
*/
|
*/
|
||||||
addSessionAutoApproveRule(key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void;
|
addSessionAutoApproveRule(chatSessionId: string, key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all session-scoped auto-approve rules.
|
* Get all session-scoped auto-approve rules for a specific chat session.
|
||||||
* @returns A record of all session-scoped auto-approve rules
|
* @param chatSessionId The chat session ID to get rules for
|
||||||
|
* @returns A record of all session-scoped auto-approve rules for the session
|
||||||
*/
|
*/
|
||||||
getSessionAutoApproveRules(): Readonly<Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }>>;
|
getSessionAutoApproveRules(chatSessionId: string): Readonly<Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -54,10 +54,11 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
private readonly _sessionAutoApprovalEnabled = new Set<string>();
|
private readonly _sessionAutoApprovalEnabled = new Set<string>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks session-scoped auto-approve rules. These are temporary rules that last only for the
|
* Tracks session-scoped auto-approve rules per chat session. These are temporary rules that
|
||||||
* duration of the VS Code session (not persisted to disk).
|
* last only for the duration of the chat session (not persisted to disk).
|
||||||
|
* Map<chatSessionId, Record<ruleKey, ruleValue>>
|
||||||
*/
|
*/
|
||||||
private readonly _sessionAutoApproveRules: Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }> = {};
|
private readonly _sessionAutoApproveRules = new Map<string, Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }>>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ILogService private readonly _logService: ILogService,
|
@ILogService private readonly _logService: ILogService,
|
||||||
@@ -72,6 +73,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
this._hasHiddenToolTerminalContext = TerminalChatContextKeys.hasHiddenChatTerminals.bindTo(this._contextKeyService);
|
this._hasHiddenToolTerminalContext = TerminalChatContextKeys.hasHiddenChatTerminals.bindTo(this._contextKeyService);
|
||||||
|
|
||||||
this._restoreFromStorage();
|
this._restoreFromStorage();
|
||||||
|
|
||||||
|
// Clear session auto-approve rules when chat sessions end
|
||||||
|
this._register(this._chatService.onDidDisposeSession(e => {
|
||||||
|
for (const resource of e.sessionResource) {
|
||||||
|
const sessionId = LocalChatSessionUri.parseLocalSessionId(resource);
|
||||||
|
if (sessionId) {
|
||||||
|
this._sessionAutoApproveRules.delete(sessionId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void {
|
registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void {
|
||||||
@@ -320,11 +331,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
return this._sessionAutoApprovalEnabled.has(chatSessionId);
|
return this._sessionAutoApprovalEnabled.has(chatSessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
addSessionAutoApproveRule(key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void {
|
addSessionAutoApproveRule(chatSessionId: string, key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void {
|
||||||
this._sessionAutoApproveRules[key] = value;
|
let sessionRules = this._sessionAutoApproveRules.get(chatSessionId);
|
||||||
|
if (!sessionRules) {
|
||||||
|
sessionRules = {};
|
||||||
|
this._sessionAutoApproveRules.set(chatSessionId, sessionRules);
|
||||||
|
}
|
||||||
|
sessionRules[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSessionAutoApproveRules(): Readonly<Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }>> {
|
getSessionAutoApproveRules(chatSessionId: string): Readonly<Record<string, boolean | { approve: boolean; matchCommandLine?: boolean }>> {
|
||||||
return this._sessionAutoApproveRules;
|
return this._sessionAutoApproveRules.get(chatSessionId) ?? {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
this._denyListCommandLineRules = denyListCommandLineRules;
|
this._denyListCommandLineRules = denyListCommandLineRules;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCommandAutoApproved(command: string, shell: string, os: OperatingSystem): ICommandApprovalResultWithReason {
|
isCommandAutoApproved(command: string, shell: string, os: OperatingSystem, chatSessionId?: string): ICommandApprovalResultWithReason {
|
||||||
// Check if the command has a transient environment variable assignment prefix which we
|
// Check if the command has a transient environment variable assignment prefix which we
|
||||||
// always deny for now as it can easily lead to execute other commands
|
// always deny for now as it can easily lead to execute other commands
|
||||||
if (transientEnvVarRegex.test(command)) {
|
if (transientEnvVarRegex.test(command)) {
|
||||||
@@ -107,7 +107,7 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check session allow rules (session deny rules can't exist)
|
// Check session allow rules (session deny rules can't exist)
|
||||||
for (const rule of this._getSessionRules().allowListRules) {
|
for (const rule of this._getSessionRules(chatSessionId).allowListRules) {
|
||||||
if (this._commandMatchesRule(rule, command, shell, os)) {
|
if (this._commandMatchesRule(rule, command, shell, os)) {
|
||||||
return {
|
return {
|
||||||
result: 'approved',
|
result: 'approved',
|
||||||
@@ -137,7 +137,7 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isCommandLineAutoApproved(commandLine: string): ICommandApprovalResultWithReason {
|
isCommandLineAutoApproved(commandLine: string, chatSessionId?: string): ICommandApprovalResultWithReason {
|
||||||
// Check the config deny list first to see if this command line requires explicit approval
|
// Check the config deny list first to see if this command line requires explicit approval
|
||||||
for (const rule of this._denyListCommandLineRules) {
|
for (const rule of this._denyListCommandLineRules) {
|
||||||
if (rule.regex.test(commandLine)) {
|
if (rule.regex.test(commandLine)) {
|
||||||
@@ -150,7 +150,7 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check session allow list (session deny rules can't exist)
|
// Check session allow list (session deny rules can't exist)
|
||||||
for (const rule of this._getSessionRules().allowListCommandLineRules) {
|
for (const rule of this._getSessionRules(chatSessionId).allowListCommandLineRules) {
|
||||||
if (rule.regex.test(commandLine)) {
|
if (rule.regex.test(commandLine)) {
|
||||||
return {
|
return {
|
||||||
result: 'approved',
|
result: 'approved',
|
||||||
@@ -176,7 +176,7 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getSessionRules(): {
|
private _getSessionRules(chatSessionId?: string): {
|
||||||
denyListRules: IAutoApproveRule[];
|
denyListRules: IAutoApproveRule[];
|
||||||
allowListRules: IAutoApproveRule[];
|
allowListRules: IAutoApproveRule[];
|
||||||
allowListCommandLineRules: IAutoApproveRule[];
|
allowListCommandLineRules: IAutoApproveRule[];
|
||||||
@@ -187,7 +187,11 @@ export class CommandLineAutoApprover extends Disposable {
|
|||||||
const allowListCommandLineRules: IAutoApproveRule[] = [];
|
const allowListCommandLineRules: IAutoApproveRule[] = [];
|
||||||
const denyListCommandLineRules: IAutoApproveRule[] = [];
|
const denyListCommandLineRules: IAutoApproveRule[] = [];
|
||||||
|
|
||||||
const sessionRulesConfig = this._terminalChatService.getSessionAutoApproveRules();
|
if (!chatSessionId) {
|
||||||
|
return { denyListRules, allowListRules, allowListCommandLineRules, denyListCommandLineRules };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionRulesConfig = this._terminalChatService.getSessionAutoApproveRules(chatSessionId);
|
||||||
for (const [key, value] of Object.entries(sessionRulesConfig)) {
|
for (const [key, value] of Object.entries(sessionRulesConfig)) {
|
||||||
if (typeof value === 'boolean') {
|
if (typeof value === 'boolean') {
|
||||||
const { regex, regexCaseInsensitive } = this._convertAutoApproveEntryToRegex(key);
|
const { regex, regexCaseInsensitive } = this._convertAutoApproveEntryToRegex(key);
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, options.shell, options.os));
|
const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, options.shell, options.os, options.chatSessionId));
|
||||||
const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(options.commandLine);
|
const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(options.commandLine, options.chatSessionId);
|
||||||
const autoApproveReasons: string[] = [
|
const autoApproveReasons: string[] = [
|
||||||
...subCommandResults.map(e => e.reason),
|
...subCommandResults.map(e => e.reason),
|
||||||
commandLineResult.reason,
|
commandLineResult.reason,
|
||||||
|
|||||||
Reference in New Issue
Block a user