security: agent-shell WS tokens and dependency audit fixes (#409)

Replace spoofable Host/Origin WebSocket auth with short-lived bootstrap
tokens minted over the existing local-operator HTTP path. Docker/browser
shell sessions prefetch a token before connecting; loopback peers remain
unchanged.

Also bump backend ws to 8.21.0 and refresh frontend lockfile to clear
npm audit findings (dev toolchain only for frontend).

Fixes #405, #406, #407

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Shadowbroker
2026-06-18 16:40:36 -06:00
committed by GitHub
parent 013849ad1f
commit 91c76ad1bd
8 changed files with 358 additions and 152 deletions
@@ -12,7 +12,7 @@ import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';
import { resolveAgentShellWsUrl } from '@/lib/agentShellWs';
import { mintAgentShellWsToken, resolveAgentShellWsUrl } from '@/lib/agentShellWs';
@@ -302,11 +302,12 @@ export default function AgentShellPanel({ active, expanded, onExpandedChange }:
const ws = new WebSocket(resolveAgentShellWsUrl(storedCwd));
void (async () => {
const wsToken = await mintAgentShellWsToken();
const ws = new WebSocket(resolveAgentShellWsUrl(storedCwd, wsToken ?? undefined));
ws.binaryType = 'arraybuffer';
ws.binaryType = 'arraybuffer';
wsRef.current = ws;
wsRef.current = ws;
@@ -423,7 +424,7 @@ export default function AgentShellPanel({ active, expanded, onExpandedChange }:
}
});
})();
}, []);
+20 -1
View File
@@ -1,4 +1,21 @@
export function resolveAgentShellWsUrl(cwd?: string): string {
export async function mintAgentShellWsToken(): Promise<string | null> {
if (typeof window === 'undefined') return null;
try {
const res = await fetch('/api/agent-shell/ws-token', {
method: 'POST',
credentials: 'same-origin',
cache: 'no-store',
});
if (!res.ok) return null;
const body = (await res.json()) as { token?: string };
const token = String(body?.token || '').trim();
return token || null;
} catch {
return null;
}
}
export function resolveAgentShellWsUrl(cwd?: string, wsToken?: string): string {
if (typeof window === 'undefined') return '';
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
const host = window.location.hostname || '127.0.0.1';
@@ -11,6 +28,8 @@ export function resolveAgentShellWsUrl(cwd?: string): string {
const params = new URLSearchParams();
const trimmed = String(cwd || '').trim();
if (trimmed) params.set('cwd', trimmed);
const token = String(wsToken || '').trim();
if (token) params.set('ws_token', token);
const query = params.toString();
return `${protocol}://${host}:${port}/api/agent-shell/ws${query ? `?${query}` : ''}`;
}