fix(guard): only echo rivet websocket subprotocol when client offers it#5307
fix(guard): only echo rivet websocket subprotocol when client offers it#5307NathanFlurry wants to merge 1 commit into
Conversation
…bprotocol when client offers it
|
🚅 Deployed to the rivet-pr-5307 environment in rivet-frontend
|
Code ReviewOverviewThis PR fixes two related WebSocket handshake bugs in the guard:
The fix is well-targeted and both changes are correct. Correctness
IssuesComment style violates CLAUDE.md CLAUDE.md says: "Never write multi-paragraph docstrings or multi-line comment blocks - one short line max." The 7-line block comment above The same applies to the 3-line inline comment blocks at both call sites in No automated tests The PR description validates the fix against a real local build, which is good for browser-specific behavior. However, there are no automated tests covering:
A unit test for Minor Observations
SummaryThe fix is correct, minimal, and well-explained. Two things to address before merging:
Generated with Claude Code |
Problem
Browser WebSocket clients that connect to the actor gateway with a plain
WebSocket(norivetsubprotocol) cannot connect. Two issues in the guard:Unsolicited subprotocol echo. The gateway unconditionally added
Sec-WebSocket-Protocol: rivetto the handshake response (guard-core/proxy_service.rs, both the normal and error-proxy upgrade paths). Per RFC 6455 a server may only select a subprotocol the client offered, and browsers reject a handshake response that echoes an unsolicited one. RivetKit's own browser clients offerrivet, so they were unaffected, but any non-RivetKit raw client (e.g. tldraw'suseSync) was rejected by the browser with:Mandatory subprotocol header on the query-gateway path.
read_gateway_token_for_path_based(used by the?rvt-method=...query routing path) required aSec-WebSocket-Protocolheader purely to look for an optionalrivet_token.*entry, throwingmissing_headerwhen absent. The gateway token is optional (auth, when required, is enforced downstream), so a plain browser client with no token was rejected before the upgrade completed.Fix
Sec-WebSocket-Protocol: rivetonly when the client actually offered it (newclient_offered_rivet_subprotocolhelper, applied at both upgrade sites).read_gateway_token_for_path_basedtreat the subprotocol header as optional for WebSockets: returnNone(no token) when it is absent instead of erroring.The query-gateway path already routes by URL (
/gateway/{name}?rvt-namespace=...&rvt-method=getOrCreate&rvt-key=...), so no subprotocol is needed for routing. RivetKit clients that offerrivetare unchanged (they still get the echo).Validation
Verified end-to-end against a local build of this branch: a tldraw multiplayer whiteboard (plain
useSyncbrowser WebSocket → query gateway → actoronWebSockethostingTLSocketRoom) connects, and edits sync bidirectionally between two browser sessions with live presence. Before this change the handshake was rejected by the browser; after it, the WebSocket connects and routes to the actor.🤖 Generated with Claude Code