Skip to content

fix(relay): detect CRLFCRLF across chunks + cap drain buffer#427

Merged
intendednull merged 1 commit into
mainfrom
claude/issue-238-bootstrap-crlf-cap
Apr 27, 2026
Merged

fix(relay): detect CRLFCRLF across chunks + cap drain buffer#427
intendednull merged 1 commit into
mainfrom
claude/issue-238-bootstrap-crlf-cap

Conversation

@intendednull
Copy link
Copy Markdown
Owner

Why

bootstrap drain loop in handle_bootstrap_request_after_line reads 1 KB chunks looking for HTTP end-of-headers \r\n\r\n. but chunk.windows(4).any(...) only scans latest chunk. marker straddling two reads = miss → loop spins until BOOTSTRAP_IO_TIMEOUT (5s). also no cap on chunk count: peer can stream forever w/ no marker.

low severity (path is rate-limited by MAX_CONCURRENT_BOOTSTRAP_CONNECTIONS semaphore + 5s deadline), but a robustness gap worth closing.

Fix

accumulate every read into bounded Vec + search whole buffer each iteration → cross-chunk marker found. cap = BOOTSTRAP_DRAIN_BUFFER_CAP = 8 KiB, matches PROXY_REQUEST_LINE_BUFFER so relay applies one symmetric header budget. exceed cap → tracing::warn one line + Err(InvalidData)dispatch_connection closes connection. seed accumulator w/ trailing 3 bytes of already_read so marker straddling request-line/chunk boundary is also caught.

rejected: rolling 4-byte tail across iterations. simpler in bytes but harder to reason about (short reads, seeded state, off-by-one risk). 8 KiB cost trivial vs clarity gain.

Verify

  • cargo fmt --check clean
  • cargo clippy -p willow-relay --all-targets -- -D warnings clean
  • cargo test -p willow-relay 12/12 pass (10 existing + 2 new)
  • new test proxy_bootstrap_detects_crlf_crlf_split_across_chunks — sends \r\n\r then pause then \n; asserts response in <2s (would hang to 5s deadline w/o fix)
  • new test proxy_bootstrap_closes_when_drain_buffer_cap_exceeded — streams 16 KiB no marker; asserts EOF in <2s w/ no 200 OK

Closes #238


Generated by Claude Code

bootstrap drain loop's `chunk.windows(4).any(...)` only inspected
latest read. CRLFCRLF straddling two reads = miss + spin to 5s
deadline. no chunk-count cap either.

now: accumulate into bounded buffer (8 KiB, matches
PROXY_REQUEST_LINE_BUFFER for symmetric header budget), search
whole buffer each iteration. seed w/ trailing 3 bytes of
already_read so request-line/chunk boundary also covered. bail
w/ tracing::warn + InvalidData err on cap overflow → connection
closes promptly. tests: cross-chunk detection + cap-overflow
bail.

rejected: rolling 4-byte tail. simpler in bytes but harder to
reason about correctness around edge cases (short reads, seeded
state). 8 KiB buffer cost trivial vs clarity gain.

Closes #238
@intendednull intendednull merged commit 99983d8 into main Apr 27, 2026
7 checks passed
@intendednull intendednull deleted the claude/issue-238-bootstrap-crlf-cap branch April 27, 2026 20:57
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.

[SEC-V-09] Bootstrap request-drain loop misses CRLF spanning chunk boundaries, no read-count cap

2 participants