-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Brainstorm server WebSocket lacks Origin validation, enabling cross-origin prompt injection #1014
Description
Preamble
Hello! I asked Claude to review the latest main of this project, mostly for current data/secret exfiltration attempts.
The only thing it surfaced is the following websocket issue. It would be pretty tough to attack but at 125k+ stars, you've got a high-value target here 😅
Apologies if there is a reason it missed why this is not actually feasible in practice. I'm not up on all my CORS browser protections specifics, but this reads as theoretically plausible (albeit impractical) to me.
Summary
The brainstorm companion server (skills/brainstorming/scripts/server.cjs) accepts WebSocket connections from any origin without validating the Origin header. Combined with the random-but-discoverable port, a malicious webpage visited while a brainstorm session is active could connect to the server and inject arbitrary content into the AI's event stream.
Attack Scenario
- User starts a brainstorm session; server binds to
ws://127.0.0.1:<random_port> - User visits a malicious webpage (e.g. in another tab)
- The page scans localhost ports to find the brainstorm server (feasible via WebSocket connection timing)
- The page connects and sends a crafted event:
{"type": "click", "choice": "x", "text": "<injected instructions>", "timestamp": 1} - This is written verbatim to
state_dir/events - On the next turn, Claude reads the events file as trusted user feedback and may act on the injected content
The /files/ HTTP endpoint has the same exposure: once the port is found, any local page can read brainstorm files served from CONTENT_DIR.
Root Cause
handleUpgrade in server.cjs only checks for the presence of Sec-WebSocket-Key and does not inspect the Origin header:
function handleUpgrade(req, socket) {
const key = req.headers['sec-websocket-key'];
if (!key) { socket.destroy(); return; }
// ← no Origin checkFix
Validate the Origin header in handleUpgrade. Legitimate connections come from the browser navigating directly to http://localhost:<port> (Origin is null or matches the server's own host):
function handleUpgrade(req, socket) {
const key = req.headers['sec-websocket-key'];
if (!key) { socket.destroy(); return; }
const origin = req.headers['origin'];
const allowedOrigins = [
\`http://localhost:\${PORT}\`,
\`http://127.0.0.1:\${PORT}\`,
];
if (origin && !allowedOrigins.includes(origin)) {
socket.destroy();
return;
}
// ...For defense in depth, a random session token could also be added to the WebSocket URL (e.g. ws://localhost:<port>/<token>) so that even a page that guesses the port cannot connect without knowing the token.
Severity
Medium. Requires the user to visit a malicious page during an active brainstorm session, and exploitation depends on port scanning localhost — which most browsers allow for WebSocket connections. Impact is prompt injection into an active Claude session.