Public: anyone can read this; the crew can change it.
No request has the forge read an unbounded body #762
Check · Review · Land · Live done — Landed and live in production.
Closes #698
shipped production is serving it for issue #698 — its issue is already done deploy d5b2fe2
Sign in to join this conversation.
new above
offline
Closes #698
No request a stranger can make has the forge read an unbounded body. Settled by measuring Bun 1.3 first; the facts that shaped this:
maxRequestBodySizebinds only a declared length. A chunked body passed a 1 KiB limit at 5 KB. The listener's ceiling is still set deliberately (512 MiB, the largest git push), but every reader counts the stream itself.fetchreuses a connection even when the answer saysConnection: close. With a reader that just stopped (or cancelled), the next request on that connection was read out of the leftover body and answered with a bare 400, as #705 found. So past a cap the reader drains at most 1 MiB more and drops it, which keeps the connection framed: the next request is read as itself. A body past even that budget is abuse; reading stops and the connection is closed once answered (server.timeout, plusConnection: close). A proxy that could not finish writing a body never reuses its connection.What changed
src/http/limits.ts: the caps in one place,readBounded(counts, stops, drains or closes),settleBodyfor unread bodies,countedfor streams. The server arms a per-request close, so a refusal found mid-stream (a push past its cap after git started answering) still ends the connection.POST /api/v1/users,/api/v1/sessions,/api/v1/tokens,/oauth/register, and the signin, signup and report forms. Reports and MCP already used the bounded reader. The sharedbody()andreadForm()are now bounded too (16 MiB);readFormparses the kept bytes, urlencoded or multipart.- A push's pack streams to git counted against 512 MiB, and a fetch's negotiation against 16 MiB. - A compressed fetch was read whole and inflated with no limit. It is now read within 1 MiB and inflated with
maxOutputLength. A streamingDecompressionStreamwas tried and dropped: when it is cut off, Bun's adapter throws an uncaught error into the process, and Bun exits on one. - Writes to git's stdin are now awaited, and they stop once git has exited. Before this, an un-awaited write under backpressure left a loose EPIPE whenever git quit early. That was harmless as it stood, but the same class of error is fatal once it escapes.docs/ARCHITECTURE.mdnames the caps (concurrency invariant 6).Tests (
test/body-limits.test.ts, in the gate, 20):Also run: git, reports, report-discovery, oauth-discovery, mcp, web, web-editor, web-ui and edge-auth, all green. (
test/settings.test.tsfails on main from #675's reserved "owner"; fixed in #759.)🤖 Generated with Claude Code