Skip to content

Add WebSocket fragment timeout and size limits#29

Open
carpentry-agent[bot] wants to merge 1 commit into
mainfrom
claude/ws-frag-limits
Open

Add WebSocket fragment timeout and size limits#29
carpentry-agent[bot] wants to merge 1 commit into
mainfrom
claude/ws-frag-limits

Conversation

@carpentry-agent

Copy link
Copy Markdown

Summary

Fragment accumulation in ws-frag-bufs previously had no TTL and relied on App.max-request-size for its only size check. A malicious client could send an initial fragment and never complete it, leaking memory indefinitely. This builds on the slowloris protection work in PR #27.

Changes

  • ConnState.ws-frag-start — new (Map Int Int) field that records the System.time when each connection begins accumulating fragments. Cleaned up on completion, protocol error, size limit breach, and connection teardown.

  • App.ws-frag-ttl (default 30 seconds) — sweep-idle now checks whether any WebSocket connection has been accumulating fragments longer than this threshold and closes it if so. Setting to 0 disables the check.

  • App.ws-max-frag-size (default 1 MiB) — separate configurable limit for accumulated fragment size, independent of App.max-request-size. Checked when the first fragment arrives and on each continuation frame. Allows operators to tune WebSocket message limits without affecting HTTP request sizes.

New constants

Constant Default Purpose
App.ws-frag-ttl 30 Max seconds to complete a fragmented message
App.ws-max-frag-size 1048576 Max accumulated fragment bytes per connection

Tests

Added tests verifying the default values of App.ws-frag-ttl and App.ws-max-frag-size. All existing tests continue to pass.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

Fragment accumulation in ws-frag-bufs previously had no TTL or size
limit independent of max-request-size. A malicious client could send
an initial fragment and never complete it, leaking memory indefinitely.

- ConnState gains ws-frag-start (Map Int Int) to track when each
  connection began accumulating fragments
- App.ws-frag-ttl (default 30s): sweep-idle closes connections where
  fragments have been accumulating longer than this threshold
- App.ws-max-frag-size (default 1 MiB): separate configurable limit
  for accumulated fragment size, checked on first fragment and each
  continuation frame
- Fragment start timestamps are cleaned up on completion, protocol
  error, size limit breach, and connection teardown

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Build & Tests

CI passes (macos-latest — the only platform in this repo's CI matrix). Tests pass, including the two new assertions for ws-frag-ttl and ws-max-frag-size defaults.

Findings

Thorough and well-executed security hardening. Detailed observations:

  1. Cleanup coverage is complete. ws-frag-start is removed at every exit point: protocol error (new message during fragmentation, line 1708), accumulated size limit breach (line 1737), message completion (line 1756), unfragmented message during fragmentation (line 1781), and connection teardown via conn-cleanup (line 1494). I checked each one against the original fragment cleanup sites and all are covered.

  2. sweep-idle TTL enforcement (lines 2098–2106): The letlet-do change is correct — the cond for ping/pong and the when for fragment TTL both execute sequentially for each WebSocket connection. The guard (> App.ws-frag-ttl 0) correctly allows disabling the feature. If queue-close was already called by the ping/pong logic for a dead client, calling it again for stale fragments is harmless (double-add to to-close array, conn-cleanup removes already-removed map entries as no-ops).

  3. First-fragment size check (line 1711): Correctly gates on (> plen App.ws-max-frag-size) before storing the payload — no wasted allocation on oversized first fragments.

  4. Continuation frame size check (line 1732): Changed from App.max-request-size to App.ws-max-frag-size — correct and intentional. Both default to 1048576, so no behavioral change with default settings, but operators can now tune WebSocket fragment limits independently.

  5. ConnState initializer (line 2201): 19 fields, 19 initializers (18 {} + 1 []). The additional {} for ws-frag-start is in the right position.

  6. CHANGELOG updated — good.

  7. Test coverage: The new tests verify the constant defaults, which is the right thing for compile-time checking. Behavioral tests for TTL enforcement and size limits are hard to write without a test harness that controls System.time, so their absence is understandable.

No bugs found.

Verdict: merge

Solid security hardening that completes the fragment accumulation protection. Clean code, CHANGELOG updated, CI green. Ready to merge.

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.

0 participants