testing/cross_backend/body_size_smuggling.ts

Cross-backend request-smuggling probe for the body-size limit's connection handling — the security sibling of body_size.ts.

When the server caps the request body it answers 413 on the Content-Length header. The strong (defense-in-depth) posture is to close the connection *without reading the oversized body*: HTTP/1.1 forbids reusing a keep-alive connection whose request body wasn't consumed, because unread body bytes would be parsed as the start of the next request — a classic request-smuggling vector. This suite probes the boundary by pipelining: it opens a raw TCP socket and sends, in one write, an oversized POST immediately followed by a second GET. The assertion forks on the backend's declared oversized_reject_closes_connection capability:

  • Closes (Node / Deno / hyper) — the reject closes the socket with the GET bytes unconsumed, so at most one response comes back. <= 1 rather than "exactly the 413" because the impls close differently at the TCP level (node-server graceful close delivers the 413 first; hyper's RST can drop the in-flight 413 before the client reads it), so demanding a cleanly-read 413 would be flaky.
  • Drains + keepalives (Bun)Bun.serve reads the full declared Content-Length body and answers the *correctly-framed* pipelined GET, so two responses come back. This is not a smuggle: the GET is delimited by the body's Content-Length, not the unread body reinterpreted as a request — Bun answers it with a clean 400 (missing method), not the x body bytes reparsed. The security property asserted here is no desync (<= 2): a real desync would reframe the 1 MiB of x into bogus request lines and push the count past two.

Either way the oversized body is rejected *with* a 413 — pinned reliably over fetch by describe_body_size_cross_tests; this test owns only the connection-handling half. A positive control (two pipelined requests → >= 2 responses) proves a second response *would* be seen if a trailing request were processed — without it the close-posture <= 1 would be vacuous on a server that never reuses connections — and that the counter isn't undercounting.

Raw-socket by necessity (the FetchTransport can't pipeline two requests on one connection), so — unlike body_size.ts — this is cross-process only (no in-process leg; there is no socket in-process). The connection-close half is capability-gated; the no-desync half holds on every spine.

Cited property: docs/security.md §"Body Size Limiting" (connection handling on oversized reject).

$lib-free by contract (relative + node: specifiers only).

view source

Declarations
#

2 declarations

BodySizeSmugglingCrossTestOptions
#

testing/cross_backend/body_size_smuggling.ts view source

BodySizeSmugglingCrossTestOptions import type {BodySizeSmugglingCrossTestOptions} from '@fuzdev/fuz_app/testing/cross_backend/body_size_smuggling.js';

Options for the smuggling probe — needs the raw URL, not a transport.

base_url

Base URL the spawned backend is reachable at (e.g. http://localhost:1178).

type string

readonly

rpc_path?

RPC endpoint path to target. Default /api/rpc.

type string

readonly

closes_connection?

Whether the backend closes the connection on an oversized-body reject without reading the body (capabilities.oversized_reject_closes_connection). true (default) demands the strong posture — the pipelined GET is never reached, so at most one response comes back. false (Bun) relaxes to the no-desync property: the body is drained on Content-Length and the pipelined GET is framed correctly, so at most two responses come back and the body bytes are never reparsed as a request. Default true so a consumer that forgets to declare the flag fails loud rather than silently accepting a drain.

type boolean

readonly

describe_body_size_smuggling_cross_tests
#

Depends on
#