From 70131f22712e48bb9fad92e183846755e723f2f1 Mon Sep 17 00:00:00 2001 From: nioasoft Date: Thu, 5 Feb 2026 21:08:46 +0200 Subject: [PATCH] fix: accept WebSocket before validation to prevent opaque 403 errors All WebSocket endpoints now call websocket.accept() before any validation checks. Previously, closing the connection before accepting caused Starlette to return an opaque HTTP 403 instead of a meaningful error message. Changes: - Server: Accept WebSocket first, then send JSON error + close with 4xxx code if validation fails (expand, spec, assistant, terminal, main project WS) - Server: ConnectionManager.connect() no longer calls accept() to avoid double-accept - UI: Gate expand button and keyboard shortcut on hasSpec - UI: Skip WebSocket reconnection on application error codes (4000-4999) - UI: Update keyboard shortcuts help text Co-Authored-By: Claude Opus 4.6 --- server/routers/terminal.py | 4 +--- server/websocket.py | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/server/routers/terminal.py b/server/routers/terminal.py index ff6cbfd..6845a27 100644 --- a/server/routers/terminal.py +++ b/server/routers/terminal.py @@ -225,9 +225,7 @@ async def terminal_websocket(websocket: WebSocket, project_name: str, terminal_i await websocket.accept() # Validate project name - try: - project_name = validate_project_name(project_name) - except Exception: + if not validate_project_name(project_name): await websocket.send_json({"type": "error", "message": "Invalid project name"}) await websocket.close( code=TerminalCloseCode.INVALID_PROJECT_NAME, reason="Invalid project name" diff --git a/server/websocket.py b/server/websocket.py index 2fdc22f..e660064 100644 --- a/server/websocket.py +++ b/server/websocket.py @@ -728,9 +728,7 @@ async def project_websocket(websocket: WebSocket, project_name: str): # Always accept WebSocket first to avoid opaque 403 errors await websocket.accept() - try: - project_name = validate_project_name(project_name) - except Exception: + if not validate_project_name(project_name): await websocket.send_json({"type": "error", "content": "Invalid project name"}) await websocket.close(code=4000, reason="Invalid project name") return @@ -885,8 +883,7 @@ async def project_websocket(websocket: WebSocket, project_name: str): break except json.JSONDecodeError: logger.warning(f"Invalid JSON from WebSocket: {data[:100] if data else 'empty'}") - except Exception as e: - logger.warning(f"WebSocket error: {e}") + except Exception: break finally: