Deep audit 2026-09-06, finding F5 (security, moderate). Survived refutation with amendments.
Mechanism. bodyLimit is registered before timeoutMiddleware (src/server.ts:53-63), so the per-request timeout never covers body receipt. For Transfer-Encoding: chunked bodies, Hono's body-limit middleware (4.13.3, node_modules/hono/dist/middleware/body-limit/index.js) fully buffers every chunk and only rejects once size > maxSize. The only bound on the drip phase is Bun's socket idleTimeout (derived 35s from UPVECTOR_REQUEST_TIMEOUT), which a chunk arriving every <35s re-arms indefinitely. No concurrent-request limit exists in the stack.
Trigger. Any authenticated client sends a chunked upload dribbling 1 byte per interval shorter than the idle timeout: buffered memory is held indefinitely per connection (bounded at 32 MiB by bodyLimit, but the time axis is unbounded), re-armed connection-by-connection after each 413.
Refuter amendments. Per-connection magnitude is capped at maxSize (32 MiB) — the unbounded axis is time, not per-connection GB. Content-Length bodies are unaffected (header check only; timeout covers handler-side parse). Authenticated-only.
Candidate mitigations (to assess).
- Idle/chunk-gap timeout on body receipt (wrap the body reader before bodyLimit).
- Global concurrent-connections or in-flight-bytes semaphore.
- Upstream: raise with hono (body-limit buffering behavior).
Triage guidance: docs/RUNBOOK.md. Audit procedure: docs/workflows/deep-audit.md.
Deep audit 2026-09-06, finding F5 (security, moderate). Survived refutation with amendments.
Mechanism.
bodyLimitis registered beforetimeoutMiddleware(src/server.ts:53-63), so the per-request timeout never covers body receipt. ForTransfer-Encoding: chunkedbodies, Hono's body-limit middleware (4.13.3,node_modules/hono/dist/middleware/body-limit/index.js) fully buffers every chunk and only rejects oncesize > maxSize. The only bound on the drip phase is Bun's socketidleTimeout(derived 35s fromUPVECTOR_REQUEST_TIMEOUT), which a chunk arriving every <35s re-arms indefinitely. No concurrent-request limit exists in the stack.Trigger. Any authenticated client sends a chunked upload dribbling 1 byte per interval shorter than the idle timeout: buffered memory is held indefinitely per connection (bounded at 32 MiB by bodyLimit, but the time axis is unbounded), re-armed connection-by-connection after each 413.
Refuter amendments. Per-connection magnitude is capped at
maxSize(32 MiB) — the unbounded axis is time, not per-connection GB. Content-Length bodies are unaffected (header check only; timeout covers handler-side parse). Authenticated-only.Candidate mitigations (to assess).
Triage guidance:
docs/RUNBOOK.md. Audit procedure:docs/workflows/deep-audit.md.