Reject unmasked client WebSocket frames (RFC 6455 §5.1) - #237
Merged
swhitty merged 1 commit intoAug 10, 2026
Conversation
Validate in WSFrameEncoder.decodeClientFrame so the error surfaces through the handler's existing input stream; MessageFrameWSHandler responds with a 1002 close. decodeFrame now preserves the decoded mask; decodeClientFrame clears it so handlers can safely echo frames. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ianegordon
marked this pull request as ready for review
August 10, 2026 01:17
Contributor
Author
|
I believe the test failures are false positives, but I'll keep an eye out for failures. This is a followup PR on a resolution based on feedback in #232. LMK if there are any requested changes. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #237 +/- ##
==========================================
+ Coverage 93.00% 93.04% +0.03%
==========================================
Files 72 72
Lines 3748 3769 +21
==========================================
+ Hits 3486 3507 +21
Misses 262 262 ☔ View full report in Codecov by Harness. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The server processes unmasked client-to-server WebSocket frames as if they were valid. RFC 6455 §5.1:
Change
This reworks closed PR #232 following the architecture proposed in this review comment:
WSFrameEncoder.decodeFrame(from:)now preserves the decoded mask (payload remains stored unmasked).WSFrameEncoder.decodeClientFrame(from:)throws when a frame has no mask.HTTPConnection.switchToWebSocketdecodes the client stream via a newdecodingClientFrames(from:)adapter — the error surfaces through theAsyncThrowingStreamtheWSHandleralready receives. The violation side-channel and task-group coordination from Reject unmasked client WebSocket frames (RFC 6455 §5.1) #232 are gone;HTTPConnectionis a two-line diff.HTTPClientand the shared decoder remain permissive — servers legitimately send unmasked frames.The review's expectation that
MessageFrameWSHandler"should already catch this error, emit a protocol-error Close frame, and terminate" was verified rather than assumed: the input-stream error reachesframesOut.finish(throwing:), and theAsyncStream.protocolFrameswrapper converts it into a Close frame carryingWSCloseCode.protocolError(1002) before ending the stream.One amendment to the review's sketch:
decodeClientFrameclears the mask after validating it is present.MessageFrameWSHandlerechoes frames verbatim (ping → pong, and the client's Close frame), so a preserved mask would be re-encoded on the way out — violating §5.1 "A server MUST NOT mask any frames that it sends to the client." Clearing it also means handlers continue to observemask == nilexactly as before; no handler-visible behavior changes.Caveats
MessageFrameWSHandlerdoes this; a customWSHandlerthat never consumes its input or swallows the error can keep the connection open — the same as for every other decode error today.readFramenow surfaces a mask set by a non-conforming server instead of silently stripping it.Tests
decodeFramemask preservation (unit + stream round-trip).decodeClientFrame: unmasked → throws; masked → decoded payload, mask cleared.decodingClientFrames: masked frames delivered unmasked; unmasked frame propagates the error (not normalized away like a disconnect); clean disconnect ends the stream without error.HTTPConnectionintegration over socket pairs: unmasked frame → unmasked 1002 Close on the wire and the response loop terminates; masked frames reach a handler withmask == niland payload decoded; abrupt client disconnect completes cleanly.Full suite passes (468 tests).
🤖 Generated with Claude Code