From 2c21366cf9c4eb80a7d8c6b3617c26c9ea1b9cf6 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Mon, 20 Apr 2026 05:40:54 +0800 Subject: [PATCH] fix(security): relay security_event through processAgentEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the sidebar-agent fires security_event (canary leak, pre-spawn ML block, tool-result ML block), it POSTs to /sidebar-agent/event which dispatches through processAgentEvent. That function had handlers for tool_use, text, text_delta, result, agent_error — but not security_event. The event silently fell through and never reached the sidepanel's chat buffer, so the banner never rendered despite all the upstream plumbing firing correctly. Caught by the new full-stack E2E test (security-e2e-fullstack.test.ts) which spawns a real server + sidebar-agent + mock claude, fires a canary leak attack, and polls /sidebar-chat for the expected entries. Before this fix, the test timed out waiting for security_event to appear. Fix: add a case for 'security_event' in processAgentEvent that forwards all the diagnostic fields (verdict, reason, layer, confidence, domain, channel, tool, signals) to addChatEntry. Sidepanel.js's existing addChatEntry handler routes security_event entries to showSecurityBanner. Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/src/server.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/browse/src/server.ts b/browse/src/server.ts index 2124cc4d..6a3c7d10 100644 --- a/browse/src/server.ts +++ b/browse/src/server.ts @@ -526,6 +526,27 @@ function processAgentEvent(event: any): void { return; } + if (event.type === 'security_event') { + // Relay the security event as a chat entry so sidepanel.js's addChatEntry + // router (showSecurityBanner) sees it on the next /sidebar-chat poll. + // Preserve all the diagnostic fields the banner renders (verdict, reason, + // layer, confidence, domain, channel, tool). + addChatEntry({ + ts, + role: 'agent', + type: 'security_event', + verdict: event.verdict, + reason: event.reason, + layer: event.layer, + confidence: event.confidence, + domain: event.domain, + channel: event.channel, + tool: event.tool, + signals: event.signals, + } as any); + return; + } + // agent_start and agent_done are handled by the caller in the endpoint handler }