Skip to content

feat(ws): expose the handshake Origin on WsConnection - #11

Open
winding-lines wants to merge 1 commit into
ehsanmok:mainfrom
millfolio:feat/ws-connection-origin
Open

feat(ws): expose the handshake Origin on WsConnection#11
winding-lines wants to merge 1 commit into
ehsanmok:mainfrom
millfolio:feat/ws-connection-origin

Conversation

@winding-lines

Copy link
Copy Markdown
Contributor

Browsers do not apply the same-origin policy to ws:// connects and send no preflight, so any page a user visits can open a WebSocket to a server this process is running -- including one bound to loopback. The Origin header is the only signal in the handshake that says which site opened the socket, and today the server parses it and throws it away: _read_upgrade_request walks every header but returns only the Sec-WebSocket-Key, and WsConnection carries just _stream + _peer. A handler therefore has no way to reject a cross-site connect.

This threads it through:

  • _WsUpgradeRequest -- a small carrier for the handshake fields the server keeps (key + origin). Both parsers now return it, so the byte-buffer and stream paths stay mirror images of each other as their docstrings promise.
  • WsConnection.origin, populated on both WsServer accept paths (the struct-handler loop and _handle_ws_connection).

The value is retained verbatim, never validated: the library has no way to know which origins a deployment trusts, so policy stays with the handler. Absent Origin yields the empty string rather than an error -- non-browser clients routinely omit it, and rejecting those would break every existing non-browser client.

The new constructor parameter is defaulted, so all existing WsConnection(stream^, peer) call sites keep compiling. Callers of the two parsers move from key to .key (3 test sites, 1 example); the fuzz harness already discards the result and is unchanged.

Tests: three byte-level parser cases (present / absent / case-insensitive field name) plus an end-to-end loopback assert that the Origin reaches the handler on WsConnection.

Written with Claude

Browsers do not apply the same-origin policy to `ws://` connects and
send no preflight, so any page a user visits can open a WebSocket to a
server this process is running -- including one bound to loopback. The
`Origin` header is the only signal in the handshake that says which site
opened the socket, and today the server parses it and throws it away:
`_read_upgrade_request` walks every header but returns only the
`Sec-WebSocket-Key`, and `WsConnection` carries just `_stream` + `_peer`.
A handler therefore has no way to reject a cross-site connect.

This threads it through:

- `_WsUpgradeRequest` -- a small carrier for the handshake fields the
  server keeps (`key` + `origin`). Both parsers now return it, so the
  byte-buffer and stream paths stay mirror images of each other as
  their docstrings promise.
- `WsConnection.origin`, populated on both `WsServer` accept paths (the
  struct-handler loop and `_handle_ws_connection`).

The value is retained verbatim, never validated: the library has no way
to know which origins a deployment trusts, so policy stays with the
handler. Absent `Origin` yields the empty string rather than an error --
non-browser clients routinely omit it, and rejecting those would break
every existing non-browser client.

The new constructor parameter is defaulted, so all existing
`WsConnection(stream^, peer)` call sites keep compiling. Callers of the
two parsers move from `key` to `.key` (3 test sites, 1 example); the
fuzz harness already discards the result and is unchanged.

Tests: three byte-level parser cases (present / absent / case-insensitive
field name) plus an end-to-end loopback assert that the Origin reaches
the handler on `WsConnection`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014RBR9rd5vFF2fTneCuVjUk
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant