fix(ws): accept same-origin WebSocket connections in default Origin allow-list
When `cors_origins` was unset, the WS endpoint only allowed `http://localhost:<port>` and `http://127.0.0.1:<port>` as origins, so a browser opening the UI via the LAN IP (e.g. `http://192.168.2.100:8765` when bound to `0.0.0.0`) had its WebSocket closed with code 4003 and never recovered — leaving the Web UI in a permanent reconnect loop. Also accept any `Origin` whose authority matches the request's `Host` header (both `http://` and `https://` schemes). Same-origin is by definition not CSWSH, so the cross-origin defence added in v0.3.0 remains intact for genuine third-party LAN pages.
This commit is contained in:
@@ -414,8 +414,13 @@ async def websocket_endpoint(
|
||||
accept_subprotocol = proto
|
||||
break
|
||||
effective_token = subprotocol_token or token
|
||||
# Origin check — block CSWSH from third-party LAN pages. We accept the same
|
||||
# set of origins as CORS plus the default localhost loopback.
|
||||
# Origin check — block CSWSH from third-party LAN pages. Accept the same
|
||||
# set of origins as CORS plus the default localhost loopback, AND any
|
||||
# same-origin connection (where Origin matches the request's Host header).
|
||||
# Same-origin is inherently safe from CSWSH because CSWSH is a *cross*-
|
||||
# origin attack — without this, binding to 0.0.0.0 and accessing the UI
|
||||
# via a LAN IP would have its WebSocket rejected by the browser-sent
|
||||
# Origin, which the static allowlist can't anticipate.
|
||||
allowed_origins = set(
|
||||
settings.cors_origins
|
||||
or [
|
||||
@@ -427,8 +432,17 @@ async def websocket_endpoint(
|
||||
# Same-origin connections from native apps may omit Origin entirely; only
|
||||
# reject when an Origin is present AND not in the allow-list.
|
||||
if origin is not None and origin not in allowed_origins:
|
||||
await websocket.close(code=4003, reason="Origin not allowed")
|
||||
return
|
||||
host_header = websocket.headers.get("host", "")
|
||||
# Origin uses http/https; match against both scheme variants of Host
|
||||
# so HTTPS deployments without an explicit cors_origins still work.
|
||||
same_origin_candidates = (
|
||||
{f"http://{host_header}", f"https://{host_header}"}
|
||||
if host_header
|
||||
else set()
|
||||
)
|
||||
if origin not in same_origin_candidates:
|
||||
await websocket.close(code=4003, reason="Origin not allowed")
|
||||
return
|
||||
|
||||
# Verify token
|
||||
from ..auth import auth_enabled, get_token_label, token_label_var
|
||||
|
||||
Reference in New Issue
Block a user