Repository navigation
Description
Summary
fleet-dispatch's hand-rolled HTTP response reader does unchecked arithmetic on the peer-declared chunk size in dechunked_len — the function read_response uses to decide whether a chunked body is complete. A chunk-size line naming one of the two largest usize values panics in debug/test builds (attempt to add with overflow) and, in release builds where overflow checks are off, wraps so the completeness guard and the slice index are computed from wrapped values. The sibling decoder dechunk in the same file guards the same declared size before slicing and handles the identical input safely — the two halves of the chunked reader disagree about whether peer-declared sizes can be trusted.
This is the #5579 rule ("peer-declared sizes are never trusted") applied everywhere else in the fleet HTTP readers — fleet-proxy's http.rs caps chunk sizes against MAX_RESPONSE_BODY_BYTES with checked_add before allocating — but missed in the dispatch client.
Evidence (verified this run, laptop, origin/main 540d3bea3f)
pkgs/fleet/crates/fleet-dispatch/src/lib.rs:
:303—let size = usize::from_str_radix(size_str.trim().split(';').next()?.trim(), 16).ok()?;— the chunk size is parsed straight from the response bytes, unbounded.:308—if rest.len() < size + 2 { return None; }—size + 2is unchecked: forsize = usize::MAXorusize::MAX - 1this overflows.:311—total += size;— also unchecked.:312—rest = &rest[size + 2..];— slices at the same unchecked expression.read_response(:273, thecompleteclosure at:265-272) callsdechunked_len(body)after every read while filling a chunked body, so the panic/wrap triggers mid-exchange on response bytes alone — no local input needed beyond runningfleet dispatch status|launch|enqueueagainst an endpoint that returns such a chunk-size line.- Contrast
:326—dechunkcheckssize == 0 || rest.len() < sizebefore slicing and never adds to the declared size; andpkgs/fleet/crates/fleet-proxy/src/http.rsread_chunked, which doesout.len().checked_add(size)and refuses anything pastMAX_RESPONSE_BODY_BYTESbefore allocating.
Probe (real crate, laptop worktree ~/git/fleet/probe-dispatch-dechunk-wt at 540d3bea3f)
A temporary #[test] appended to the crate's own test module, run with cargo test -p fleet-dispatch probe_dechunked_len_huge_chunk_size -- --nocapture:
- Control:
dechunked_len(b"4\r\nWiki\r\n5\r\npedia\r\n0\r\n\r\n") == Some(9)✔ - Control:
dechunked_len(b"4\r\nWi") == None✔ - Control:
dechunk(b"ffffffffffffffff\r\nXX") == []— the decoder is safe on the same bytes ✔ dechunked_len(b"ffffffffffffffff\r\nXX")→ PANIC atcrates/fleet-dispatch/src/lib.rs:308:25: attempt to add with overflowdechunked_len(b"fffffffffffffffe\r\nXX")→ PANIC at the same line- Probe result:
PROBE DONE panics=true true, test passed (panics caught viacatch_unwind).
In a release build the same expressions wrap instead of panicking: size + 2 becomes 1 (for MAX) or 0 (for MAX - 1), the :308 guard passes against a 2-byte remainder, total wraps, and rest[size + 2..] advances by the wrapped index — the completeness verdict for the exchange is then computed from wrapped arithmetic while dechunk decodes something else, so the client can finish an exchange early on a body the decoder reads differently.
Why it matters
fleet dispatch is the thin client every seat uses to launch and enqueue sessions through pm-dispatch, over both the direct and the CONNECT-tunnel transports. Its response reader is the one hand-rolled HTTP reader in the fleet workspace that still does raw arithmetic on a peer-declared length. pm-dispatch (Bun.serve) will not emit such a chunk size itself, but the reader's contract — per #5579 and the module's own framing — is that response framing is validated before it is trusted: a buggy, compromised, or mis-proxied endpoint (the tunnel path traverses the egress proxy) turns a status/launch/enqueue call into a client panic in debug builds and a silently wrong completeness verdict in release builds, instead of the crate's normal DispatchError::Transport exit-3 path. Panics also bypass the crate's exit-code contract (0/2/3/4) entirely.
Acceptance criteria
-
dechunked_lenuses checked arithmetic (e.g.size.checked_add(2), a checked running total, and/or a bound of the declared size against the remaining bytes / the existing 64 MiB body cap) so no peer-declared chunk size can overflow, wrap, or panic in any build profile. - A chunk-size line that cannot describe a body within the buffered remainder (including the
usize::MAX/usize::MAX - 1cases) is treated as "not complete" or as a transport error — never as a completed body computed from wrapped values. - Unit tests in
fleet-dispatchpin: the two overflow sizes above, a large-but-plausible size with insufficient bytes (None/error, no panic), and the existing happy-path pins (chunked_bodies_dechunk) keep passing. -
cargo test -p fleet-dispatchpasses on the laptop or in CI.
Scope note: read_response's Content-Length handling and dechunk itself are out of scope except where the fix naturally shares a guard; the pooled-connection desync concerns of #5579 do not apply here (one exchange per connection, Connection: close).
Activity
developing-today commentedon Nov 6, 2026
CODE-1971
dezren39 commentedon Nov 6, 2026
🤖 Agent note · filed
pkgs/fleet/crates/fleet-dispatch/src/lib.rson the laptop primary at origin/main540d3bea3fwhile auditing the fleet HTTP readers after today's fleet-proxy filings (issue fleet-proxy(http): response reader caps every line but nothing caps repetition — endless 1xx heads, header lines, or trailer lines hang an exchange (and its pooled tunnel) forever #6509, issue fleet-proxy(connect-udp): request-head bound is checked only after a whole line arrives — one newline-free line accumulates without limit #6511), then verified against the real crate.dechunked_len(lib.rs:297-314) parses the peer-declared chunk size at:303and does uncheckedsize + 2at:308/:312andtotal += sizeat:311; probe worktree~/git/fleet/probe-dispatch-dechunk-wt(real fleet-dispatch crate, temporary test in the crate's own test module): controls pass (Some(9), incompleteNone,dechunksafe on the same bytes), and bothffffffffffffffffandfffffffffffffffechunk-size lines PANIC at lib.rs:308:25 "attempt to add with overflow". Full narrative in the issue body.repo-task/v1triage packet for this issue is enqueued torepo-health-code-pilotby this run (task id recorded in the goal backlog entry).dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
10 remaining items
dezren39 commentedon Nov 6, 2026
🤖 Agent note · picked up
dezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
dezren39 commentedon Nov 6, 2026
🤖 Agent note · design-architect pass 62 — adopted as Phase 3 of design #6582
files/designs/code-fleet-proxy-validated-bounded.mdin the keep-the-four-repos-healthy goal workspacedechunked_len. Independent of Phases 1–2 in coderead_response's Content-Length handling is dispositioned by Phase 4's inventory (Phase 4 of #6582 — closing read-site inventory: disposition every frame/peer value read in fleet-proxy + fleet-dispatch against the validated-and-bounded contract #6583), not fixed heredezren39 commentedon Nov 6, 2026
🤖 Agent note · handed off
opencode; handing off to the opencode-claimant per worker instruction §1b, parked pending claimant launch. Issue fleet(dispatch): dechunked_len does unchecked arithmetic on the peer-declared chunk size — overflow panics in debug, wraps in release #6512 re-verified OPEN with no hard-stop labels via gh this shift. Attempts now 3/4 — claimant binding is urgent to avoid cap exhaustion.