The room client took its relay, room service, fixture, WebTransport URL,
certificate hashes and issuer directory from its query string as given, and
followed a relay's GOAWAY to any URI. A crafted link and a press of Join would
send a joiner's camera and microphone to another origin.
- web/room/endpoint.js: an endpoint is taken only on the page's own origin
(`https://<host>`, or `wss://<host>` for a relay), never with user
information; a page served from the loopback interface (a fixture page, the
e2e harness) takes loopback addresses alone.
- app.js: `service`, `ws`, `wt`, `fixture` and `issuer_directory` are held to
it; `hashes` only on a loopback page. A refused endpoint is recorded
(`snapshot().refused`) and shown, Join stays off, and the page connects to
nothing: `enter()` checks the relay before it asks for the camera, a grant or
a session. A GOAWAY naming another origin is refused: the page closes its
session and says so.
- web/room/endpoint.test.mjs (run by e2e/run.sh): own origin, another origin,
scheme-relative, the page's host as user information, javascript:, a plain
scheme on a secure page, an HTTP page, and loopback for the harness.
- e2e scenario `origin`: a foreign `?ws=` is refused at Join and nothing
connects (no WebSocket, request, grant or camera); a foreign `?service=`,
scheme-relative, behind user information or as javascript:, is refused with
Join off; a GOAWAY to another origin is refused and the session ends. The
`service` scenario checks that every request and WebSocket of every page
stays on the page's origin, as the production `connect-src` requires.
Co-Authored-By: Claude Opus 5.5 <[email protected]>