Skip to content

Commit cc87a86

Browse files
committed
fix(viz): reject cross-origin WebSocket handshakes
The `/ws` handler enforced "localhost bind only" but otherwise accepted every incoming handshake without checking the Origin header. Browsers deliberately allow cross-origin WebSocket handshakes — the Same-Origin Policy does not apply there — so a malicious page open in the same browser could connect to ws://localhost:<port>/ws and immediately send a `cancel_session` message, killing an active RLCR loop with zero auth prompt. Reuse the existing `_origin_matches_request()` matcher (the same logic that gates mutating HTTP routes via CSRF) before adding the socket to `_ws_clients`. Connections without an Origin header are treated as same-origin (curl, server-to-server callers): the localhost bind already refuses non-loopback clients and the Origin header is effectively mandatory from browsers on the WebSocket handshake. Fixes Codex review P1 on PR PolyArch#63 (cross-origin WebSocket `cancel_session` vector). Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
1 parent 8de3545 commit cc87a86

1 file changed

Lines changed: 17 additions & 0 deletions

File tree

viz/server/app.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,23 @@ def websocket(ws):
13761376
pass
13771377
return
13781378

1379+
# Cross-origin WebSocket rejection. The HTTP side of the app
1380+
# gates mutating routes through `_enforce_csrf_protection`, but
1381+
# browsers happily let arbitrary pages open a WebSocket to
1382+
# ws://localhost:<port>/ws with no Origin check from the server.
1383+
# A `cancel_session` message over that connection would kill an
1384+
# active loop with zero auth prompt. Reuse the same request-host
1385+
# matcher so the localhost dashboard's own Origin keeps working
1386+
# while hostile origins (pages served by other projects in the
1387+
# same browser) are closed before they can send anything.
1388+
origin = request.headers.get('Origin', '').strip()
1389+
if origin and not _origin_matches_request(origin):
1390+
try:
1391+
ws.close(reason='cross-origin WebSocket rejected')
1392+
except Exception:
1393+
pass
1394+
return
1395+
13791396
with _ws_lock:
13801397
_ws_clients.add(ws)
13811398
try:

0 commit comments

Comments
 (0)