From add9e8cf3cb3feb61517e7469073d90511de760e Mon Sep 17 00:00:00 2001 From: Jeremy McSpadden Date: Mon, 16 Mar 2026 17:28:32 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20address=20PR=20review=20=E2=80=94=20CSP?= =?UTF-8?q?=20nonce,=20dead=20branch,=20restart=20cooldown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Webview CSP nonce (security): Added Content-Security-Policy meta tag with nonce-based script-src to sidebar.ts. Replaced all inline onclick handlers with data-command attributes and a single delegated event listener, which CSP requires over inline handlers. 2. Dead branch in chat-participant.ts: Removed the isSlashCommand conditional that ran identical code for both paths — slash commands and regular messages both call sendPrompt() the same way. 3. Restart loop cooldown in gsd-client.ts: Added a 60-second sliding window that tracks crash timestamps. If the process crashes more than 3 times within 60 seconds, auto-restart is disabled and an error is surfaced to the user via the onError event emitter. --- vscode-extension/src/chat-participant.ts | 10 +----- vscode-extension/src/gsd-client.ts | 19 +++++++++-- vscode-extension/src/sidebar.ts | 43 ++++++++++++++++-------- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/vscode-extension/src/chat-participant.ts b/vscode-extension/src/chat-participant.ts index 1d630fe2e..c4ba367df 100644 --- a/vscode-extension/src/chat-participant.ts +++ b/vscode-extension/src/chat-participant.ts @@ -26,9 +26,6 @@ export function registerChatParticipant( return; } - // If the message starts with /, forward as a slash command prompt - const isSlashCommand = message.startsWith("/"); - // Track streaming events while the prompt executes let agentDone = false; let totalInputTokens = 0; @@ -127,12 +124,7 @@ export function registerChatParticipant( }); try { - if (isSlashCommand) { - // Forward slash commands as regular prompts - await client.sendPrompt(message); - } else { - await client.sendPrompt(message); - } + await client.sendPrompt(message); // Wait for agent_end or cancellation await new Promise((resolve) => { diff --git a/vscode-extension/src/gsd-client.ts b/vscode-extension/src/gsd-client.ts index 19a4ddc53..20db6d327 100644 --- a/vscode-extension/src/gsd-client.ts +++ b/vscode-extension/src/gsd-client.ts @@ -86,6 +86,7 @@ export class GsdClient implements vscode.Disposable { private requestId = 0; private buffer = ""; private restartCount = 0; + private restartTimestamps: number[] = []; private readonly _onEvent = new vscode.EventEmitter(); readonly onEvent = this._onEvent.event; @@ -142,9 +143,21 @@ export class GsdClient implements vscode.Disposable { this.rejectAllPending(`GSD process exited (code=${code}, signal=${signal})`); this._onConnectionChange.fire(false); - if (this.restartCount < 3 && code !== 0 && signal !== "SIGTERM") { - this.restartCount++; - setTimeout(() => this.start(), 1000 * this.restartCount); + if (code !== 0 && signal !== "SIGTERM") { + const now = Date.now(); + this.restartTimestamps.push(now); + // Keep only timestamps within the last 60 seconds + this.restartTimestamps = this.restartTimestamps.filter(t => now - t < 60_000); + + if (this.restartTimestamps.length > 3) { + // Too many crashes within 60s — stop retrying + this._onError.fire( + `GSD process crashed ${this.restartTimestamps.length} times within 60s. Not restarting. Use "GSD: Start Agent" to retry manually.`, + ); + } else if (this.restartCount < 3) { + this.restartCount++; + setTimeout(() => this.start(), 1000 * this.restartCount); + } } }); diff --git a/vscode-extension/src/sidebar.ts b/vscode-extension/src/sidebar.ts index 684faaf32..961c56d0d 100644 --- a/vscode-extension/src/sidebar.ts +++ b/vscode-extension/src/sidebar.ts @@ -199,11 +199,14 @@ export class GsdSidebarProvider implements vscode.WebviewViewProvider { ? `
Agent is working...
` : ""; + const nonce = getNonce(); + return /* html */ ` +