Desktop: send the gateway's own Origin on gateway WebSocket upgrades - #65114
Desktop: send the gateway's own Origin on gateway WebSocket upgrades#65114Zeus-Deus wants to merge 1 commit into
Conversation
|
Thanks for the focused Desktop fix. The premise remains present on current main: The proposed helper is appropriately narrow: it only rewrites GitHub currently marks the branch conflicting; This is an automated hermes-sweeper review. |
The dashboard's DNS-rebinding guard refuses WebSocket upgrades whose Origin does not match the bound host, with 403 before accept(). Chromium stamps the window's web origin on every WS handshake and the renderer cannot override it, so a dev-mode renderer (Vite, 127.0.0.1:5174) can never open the gateway WS against a remote host: REST keeps working (the main process sends no Origin) while the WS dies pre-accept, surfacing as the opaque "Could not connect to Hermes gateway". Rewrite the Origin on gateway WS upgrades (/api/ws, /api/pub, /api/events only) to the target's own origin in the main process. The rewrite decision is electron-free and unit-tested; packaged builds were already exempt server-side via their non-web file:// origin.
0540935 to
686e435
Compare
Problem
The dashboard applies a DNS-rebinding Host/Origin guard to WebSocket upgrades (
_ws_host_origin_reasonin web_server.py): when the handshake carries an Origin header, it must match the bound dashboard host, or the upgrade is refused with 403 beforeaccept().Chromium stamps the window's web origin on every WebSocket handshake and the page cannot override it. When the desktop app runs from a source checkout (
npm run dev), the renderer is served by Vite athttp://127.0.0.1:5174, so every gateway WS upgrade carries that as its Origin. Against a remote gateway bound to another host, the socket is closed pre-accept and the app surfaces the opaque "Could not connect to Hermes gateway" — while every REST call keeps working, because those route through the Electron main process, which sends no Origin at all. That combination (REST green, WS dead) makes this miserable to debug: the gateway auth log shows ws-tickets being minted, but nows acceptedline ever follows in tui_gateway logs.I hit this connecting the desktop app on my laptop to the gateway on my home server over my tailnet. Isolating it down:
Origin: http://127.0.0.1:5174→ HTTP 403gateway.readyarrivesPackaged builds don't hit this because their window is
file://, a non-web origin the server-side guard already exempts. So this only affects running from source — which is exactly how you iterate on desktop ↔ remote gateway setups.Fix
The desktop shell owns its gateway connection, and the real auth boundary is the credential (the single-use ws-ticket minted over the authenticated cookie session). So the handshake now presents the gateway's own origin — the same trust statement the
file://exemption already makes for packaged builds:electron/gateway-ws-origin.ts— pure, electron-free rewrite decision (same pattern as connection-config.ts): onlyws:/wss:upgrades whose path ends in/api/ws,/api/pub, or/api/eventsget their Origin replaced with the target's own origin; everything else passes through untouched. Path prefixes (/hermes/api/ws) work.main.tswires it intosession.defaultSession.webRequest.onBeforeSendHeaders, filtered tows://*/*/wss://*/*.Nothing changes for real browsers hitting the dashboard; the server-side guard stays fully meaningful for them.
Related: #62301 adds a server-side allowed-hosts opt-in whose allowlist also feeds the WS origin check, so it can work around this too — but a native client shouldn't need server configuration to talk to its own configured gateway. Both changes stand on their own.
Testing
npm run typecheckcleanelectron/gateway-ws-origin.test.ts; the full electron vitest project passes (422 tests)hermes serveon my home server over Tailscale. Before the change the gateway logged ticket mints but neverws accepted; with it the upgrade is accepted and the app connects and streams normally.