fix(gateway): allow HMR sockets on every configured domain
Quality / quality (ubuntu-latest) (push) Failing after 13m52s
Quality / quality (windows-latest) (push) Canceled after 0s

The WebSocket origin check compared the browser's Origin host, which
carries the port, against configured domains, which do not. publicOrigin
only ever matches domains[0], so every other domain fell through to that
comparison and was denied purely on the port: web.localhost:3000 never
matched web.localhost.

The result was a 403 on the HMR upgrade and a client reconnecting
forever, while the page itself loaded fine because HTTP routing resolves
the Host separately.

Compares hostnames now. Unrelated and lookalike-suffix origins are still
denied, and both cases are covered by tests.

Verified through a real gateway: the HMR socket opens on both localhost
and web.localhost, and a live edit reaches the browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 19:53:27 +05:30
co-authored by Claude Opus 5
parent b3b65dddd8
commit e66d2425aa
2 changed files with 61 additions and 2 deletions
+6 -2
View File
@@ -159,7 +159,7 @@ function requestMessageBytes(value: string | ArrayBuffer | ArrayBufferView): num
return value instanceof ArrayBuffer ? value.byteLength : value.byteLength;
}
function gatewayWebSocketOriginAllowed(
export function gatewayWebSocketOriginAllowed(
req: Request,
target: Target,
configured: string[],
@@ -174,7 +174,11 @@ function gatewayWebSocketOriginAllowed(
}
if (configured.includes(origin)) return true;
if (target.publicOrigin && origin === new URL(target.publicOrigin).origin) return true;
return target.domains.some((domain) => parsed.host.toLowerCase() === domain.toLowerCase());
// Compare hostnames, not hosts: configured domains carry no port, while the
// browser's Origin does. publicOrigin above only ever matches domains[0], so
// every other domain fell through to here and was denied purely on the port,
// which left the HMR socket reconnecting forever on those hosts.
return target.domains.some((domain) => parsed.hostname.toLowerCase() === domain.toLowerCase());
}
/**