@@ -45,6 +45,32 @@ function log(msg: string) {
4545 process . stderr . write ( `[aipex-daemon] ${ msg } \n` ) ;
4646}
4747
48+ // ── Origin validation ───────────────────────────────────────────────────────
49+
50+ /**
51+ * Validate the Origin header on WebSocket upgrade requests to prevent
52+ * cross-site WebSocket hijacking (CSWSH).
53+ *
54+ * Allowed origins:
55+ * - No Origin header (Node.js clients: bridge.ts, cli.ts, aipex-cli)
56+ * - chrome-extension:// (the AIPex browser extension)
57+ * - moz-extension:// (Firefox extension equivalent)
58+ *
59+ * Rejected origins:
60+ * - http:// or https:// (web pages — attack vector for CSWSH)
61+ */
62+ function isOriginAllowed ( origin : string | undefined ) : boolean {
63+ // Node.js WebSocket clients don't send an Origin header — allow
64+ if ( ! origin ) return true ;
65+
66+ // Browser extensions are trusted clients
67+ if ( origin . startsWith ( "chrome-extension://" ) ) return true ;
68+ if ( origin . startsWith ( "moz-extension://" ) ) return true ;
69+
70+ // Reject all web page origins (http/https) — prevents CSWSH attacks
71+ return false ;
72+ }
73+
4874// ── Extension connection ────────────────────────────────────────────────────
4975
5076let extensionWs : WebSocket | undefined ;
@@ -325,6 +351,19 @@ const bridgeWss = new WebSocketServer({ noServer: true });
325351const cliWss = new WebSocketServer ( { noServer : true } ) ;
326352
327353httpServer . on ( "upgrade" , ( req , socket , head ) => {
354+ const origin = req . headers . origin ;
355+
356+ // Reject WebSocket upgrades from web page origins to prevent CSWSH.
357+ // Legitimate clients (bridge.ts, cli.ts) are Node.js processes that
358+ // don't send an Origin header. The Chrome extension sends
359+ // chrome-extension:// which is explicitly allowed.
360+ if ( ! isOriginAllowed ( origin ) ) {
361+ log ( `Rejected WebSocket upgrade from origin: ${ origin } ` ) ;
362+ socket . write ( "HTTP/1.1 403 Forbidden\r\n\r\n" ) ;
363+ socket . destroy ( ) ;
364+ return ;
365+ }
366+
328367 const pathname = new URL ( req . url ?? "/" , "http://localhost" ) . pathname ;
329368
330369 if ( pathname === "/extension" || pathname === "/" ) {
0 commit comments