fix(dashboard): allow chat websockets on insecure public bind
Allow non-loopback websocket peers when the dashboard is explicitly exposed with --host 0.0.0.0/:: and --insecure. This fixes the failure mode where /chat rendered over LAN but /api/ws and /api/events were rejected with HTTP 403, leaving the embedded TUI chat disconnected. Add regression coverage for the insecure public bind case in the dashboard websocket auth tests.
This commit is contained in:
@@ -3371,10 +3371,15 @@ _LOOPBACK_HOSTS = frozenset({"127.0.0.1", "::1", "localhost", "testclient"})
|
||||
def _ws_client_is_allowed(ws: "WebSocket") -> bool:
|
||||
"""Check if the WebSocket client IP is acceptable.
|
||||
|
||||
Loopback mode: only loopback clients allowed — the legacy
|
||||
Loopback bind: only loopback clients allowed — the legacy
|
||||
``?token=<_SESSION_TOKEN>`` path is the only auth we have, so we
|
||||
don't want LAN hosts guessing tokens.
|
||||
|
||||
All-interfaces insecure bind (``--host 0.0.0.0 --insecure`` or
|
||||
``--host :: --insecure``): allow any peer. The operator explicitly
|
||||
opted into LAN/public exposure in this mode, so the loopback-only peer
|
||||
restriction should not apply.
|
||||
|
||||
Gated mode: any peer is allowed — uvicorn's ``proxy_headers=True``
|
||||
(enabled when the OAuth gate is active so cookies can pick up
|
||||
``X-Forwarded-Proto``) rewrites ``ws.client.host`` to the
|
||||
@@ -3385,6 +3390,9 @@ def _ws_client_is_allowed(ws: "WebSocket") -> bool:
|
||||
"""
|
||||
if getattr(app.state, "auth_required", False):
|
||||
return True
|
||||
bound_host = getattr(app.state, "bound_host", "")
|
||||
if bound_host in {"0.0.0.0", "::"}:
|
||||
return True
|
||||
client_host = ws.client.host if ws.client else ""
|
||||
if not client_host:
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user