fix: address PR review — CSP nonce, dead branch, restart cooldown
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.
This commit is contained in:
parent
6ed9cd5359
commit
add9e8cf3c
3 changed files with 46 additions and 26 deletions
|
|
@ -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<void>((resolve) => {
|
||||
|
|
|
|||
|
|
@ -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<AgentEvent>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -199,11 +199,14 @@ export class GsdSidebarProvider implements vscode.WebviewViewProvider {
|
|||
? `<div class="streaming-indicator"><span class="spinner"></span> Agent is working...</div>`
|
||||
: "";
|
||||
|
||||
const nonce = getNonce();
|
||||
|
||||
return /* html */ `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
||||
<style>
|
||||
body {
|
||||
font-family: var(--vscode-font-family);
|
||||
|
|
@ -380,16 +383,16 @@ export class GsdSidebarProvider implements vscode.WebviewViewProvider {
|
|||
<div class="section-title">Controls</div>
|
||||
<div class="btn-group">
|
||||
${info.connected
|
||||
? `<button onclick="send('stop')">Stop Agent</button>
|
||||
? `<button data-command="stop">Stop Agent</button>
|
||||
<div class="btn-row">
|
||||
<button class="secondary" onclick="send('newSession')">New Session</button>
|
||||
<button class="secondary" onclick="send('switchModel')">Model</button>
|
||||
<button class="secondary" data-command="newSession">New Session</button>
|
||||
<button class="secondary" data-command="switchModel">Model</button>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="secondary" onclick="send('cycleThinking')">Thinking</button>
|
||||
<button class="secondary" onclick="send('toggleAutoCompaction')">Auto-Compact</button>
|
||||
<button class="secondary" data-command="cycleThinking">Thinking</button>
|
||||
<button class="secondary" data-command="toggleAutoCompaction">Auto-Compact</button>
|
||||
</div>`
|
||||
: `<button onclick="send('start')">Start Agent</button>`
|
||||
: `<button data-command="start">Start Agent</button>`
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -399,22 +402,25 @@ export class GsdSidebarProvider implements vscode.WebviewViewProvider {
|
|||
<div class="section-title">Actions</div>
|
||||
<div class="btn-group">
|
||||
<div class="btn-row">
|
||||
<button class="secondary" onclick="send('compact')">Compact</button>
|
||||
<button class="secondary" onclick="send('exportHtml')">Export</button>
|
||||
<button class="secondary" data-command="compact">Compact</button>
|
||||
<button class="secondary" data-command="exportHtml">Export</button>
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="secondary" onclick="send('abort')">Abort</button>
|
||||
<button class="secondary" onclick="send('listCommands')">Commands</button>
|
||||
<button class="secondary" data-command="abort">Abort</button>
|
||||
<button class="secondary" data-command="listCommands">Commands</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
<script>
|
||||
<script nonce="${nonce}">
|
||||
const vscode = acquireVsCodeApi();
|
||||
function send(command, value) {
|
||||
vscode.postMessage({ command, value });
|
||||
}
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target.closest('[data-command]');
|
||||
if (btn) {
|
||||
vscode.postMessage({ command: btn.dataset.command });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
|
@ -428,3 +434,12 @@ function escapeHtml(text: string): string {
|
|||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function getNonce(): string {
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let nonce = "";
|
||||
for (let i = 0; i < 32; i++) {
|
||||
nonce += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue