Skip to content

Enforce RFC 6455 frame rules in WebSocket writer - #14233

Open
rinopatrick wants to merge 4 commits into
aio-libs:masterfrom
rinopatrick:fix/14167-ws-control-frame-rules
Open

rinopatrick wants to merge 4 commits into
aio-libs:masterfrom
rinopatrick:fix/14167-ws-control-frame-rules

Conversation

@rinopatrick

Copy link
Copy Markdown

Closes #14167.

The writer accepted frames the reader (conforming peer) must reject: control frames over 125 bytes, close codes 1005/1006/1015/1016-2999/above 4999, and non-UTF-8 close reasons. Validation now happens in WebSocketWriter.send_frame (covers ping/pong/close plus direct send_frame callers) and close(), raising ValueError before anything hits the wire — mirroring the reader-side checks.

@agentscanapp

agentscanapp Bot commented Nov 1, 2026

Copy link
Copy Markdown

Automation signals

Activity patterns show signs of automation.

View full analysis →

Report this account →

Evidence
  • Closed PRs across many repositories: 21 PRs were closed across 3 repositories in 3h, with a spike of 21 closures on one day.
  • Very high PR volume (daily): 40 PRs within a single 24-hour window
  • Rapid PRs to repository: 8 PRs opened to rinopatrick/reign within 14s intervals
  • Multiple forks: 6 repositories forked in a single 24-hour window
  • Issue comment and PR within minutes: Commented on an issue and opened a PR to the same repo within 5 minutes, in 4 repositories (the fastest took 84s)

Last 5 PRs:

This is an automated analysis by AgentScan

@codecov

codecov Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.96970% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 99.11%. Comparing base (b4beb73) to head (7e09740).
⚠️ Report is 2 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
aiohttp/client_ws.py 50.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #14233      +/-   ##
==========================================
- Coverage   99.11%   99.11%   -0.01%     
==========================================
  Files         135      135              
  Lines       53338    53499     +161     
  Branches     2717     2732      +15     
==========================================
+ Hits        52865    53023     +158     
- Misses        359      360       +1     
- Partials      114      116       +2     
Flag Coverage Δ
Autobahn 21.69% <36.36%> (-0.01%) ⬇️
CI-GHA 99.04% <96.96%> (+<0.01%) ⬆️
OS-Linux 98.68% <96.96%> (+<0.01%) ⬆️
OS-Windows 97.47% <96.96%> (-0.01%) ⬇️
OS-macOS 98.34% <96.96%> (-0.02%) ⬇️
Py-3.11 98.28% <96.96%> (-0.01%) ⬇️
Py-3.12 98.37% <96.96%> (-0.02%) ⬇️
Py-3.13 98.36% <96.96%> (-0.01%) ⬇️
Py-3.14 98.38% <96.96%> (-0.02%) ⬇️
Py-3.15 98.39% <96.96%> (-0.01%) ⬇️
Py-3.15t 98.23% <96.96%> (-0.01%) ⬇️
Py-pypy-3.12 96.93% <96.96%> (-0.01%) ⬇️
VM-macos-latest 98.34% <96.96%> (-0.02%) ⬇️
VM-ubuntu-latest 98.68% <96.96%> (+<0.01%) ⬆️
VM-windows-latest 97.47% <96.96%> (-0.01%) ⬇️
cython-coverage 83.77% <52.94%> (-0.04%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@greptile-apps

greptile-apps Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5 Tier: apex

[Medium impact] The change affects outbound WebSocket traffic on both aiohttp client and server connections.

Not ready to merge: rejected closes can still end a usable connection, and the required changelog link is missing.

Reviews (2) · Last reviewed commit: "[pre-commit.ci] auto fixes from pre-comm..." · Reviewed by Greptile

Comment thread aiohttp/_websocket/writer.py Resolved
Comment thread aiohttp/_websocket/writer.py Outdated
Comment on lines +291 to +300
allowed = code in _ALLOWED_CLOSE_CODES or (
WS_CLOSE_CODE_MIN_APPLICATION <= code <= WS_CLOSE_CODE_MAX_APPLICATION
)
if not allowed:
raise ValueError(f"Invalid close code: {code}")
# RFC 6455 §5.5.1: the close reason must be valid UTF-8.
try:
message.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValueError("Close message must be valid UTF-8") from exc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Rejected closes end the connection

The new close-argument errors do not reach callers of either public close() method. Both methods set _closed before calling the writer, then catch these ValueErrors and close the transport. For example, await ws.close(code=1005) returns True instead of raising, and a corrected retry returns False.

Check arguments before the public methods change state, and let input errors reach the caller. The size check also needs to precede the writer's closing state change: writer.close(1000, b"x" * 124) currently sets _closing = True in finally despite sending nothing.

Comment thread CHANGES/14167.bugfix.rst
Comment on lines +1 to +6
Started enforcing :rfc:`6455` frame rules on the WebSocket writer, mirroring
what the reader already enforces: control frames (ping/pong/close) with more
than 125 bytes of payload, close frames with a code that must not appear on
the wire (1005, 1006, 1015, 1016-2999, above 4999), and close frames with a
non-UTF-8 reason are now rejected with :exc:`ValueError` instead of emitting
an invalid frame -- by :user:`rinopatrick`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Required release docs are missing

AGENTS.md requires a docs/ update for user-visible API changes and a PR-numbered symlink when a changelog uses an issue number. This PR adds new limits and errors without updating docs/client_reference.rst or docs/web_reference.rst, and CHANGES/14233.bugfix.rst is missing.

Before merging, update both reference pages and add CHANGES/14233.bugfix.rst as a symlink to 14167.bugfix.rst to satisfy these repository requirements.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +82 to +85
async def test_ping_payload_too_large(writer: WebSocketWriter) -> None:
# RFC 6455 §5.5: control frames must have ≤125 bytes of payload
with pytest.raises(ValueError, match="125"):
await writer.send_frame(b"x" * 126, WSMsgType.PING)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Tests miss invalid writes

The three new rejection tests only check that ValueError is raised. A writer that sends the invalid frame and then raises would still pass them. Assert that transport.write was not called after each rejected input, before the valid close calls in test_close_invalid_code.

@codspeed

codspeed Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

1 warning

⚠️ 49 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 102 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing rinopatrick:fix/14167-ws-control-frame-rules (7e09740) with master (a2c6c92)

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

Address review: raw close frames via send_frame() carried unchecked codes/reasons, and close() argument errors fired after _set_closed() tore the connection down. Validate up front in both wrappers and the writer; document ValueError in client/web references; changelog links PR. Closes aio-libs#14167
@rinopatrick

Copy link
Copy Markdown
Author

Addressed all four review points: (1) raw CLOSE frames via send_frame() are now validated (code + UTF-8 reason + payload shape), (2) close()/wrapper validate before _set_closed() so invalid arguments surface to the caller without tearing the connection down, (3) client/web reference docs document the new ValueError cases, changelog has the PR-numbered symlink, (4) rejection tests assert the invalid frame never hits the wire.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebSocket writer does not enforce the RFC 6455 control frame rules

1 participant