Last reviewed: 2026-10-21
Direct answer
Prevent HTTP request smuggling in a coding-agent patch by making every HTTP hop agree on message framing before the change merges. Reject ambiguous requests, especially those that contain both Transfer-Encoding and Content-Length, normalize or reject duplicate framing fields according to one documented rule, and close the connection after a framing error. Keep the same strict policy at the edge, reverse proxy, gateway, and application server; do not let a permissive parser sit behind a strict one.
The core risk is disagreement. An attacker sends bytes that the front end interprets as one request while the back end interprets as two. The second request can bypass an authorization check, poison a shared cache, or reach a route that the first hop never intended to expose. RFC 9112 says that when both Transfer-Encoding and Content-Length appear, transfer encoding overrides content length, the combination may indicate request smuggling, and an intermediary that forwards the message must remove Content-Length and process the transfer coding first. A server receiving invalid request framing should answer with 400 and close the connection.
Treat the RFC rule as a minimum protocol contract, not as permission to pass ambiguous bytes downstream. A safer gateway policy rejects the ambiguity at its first trusted boundary, records a stable reason code, and never retries the same bytes through a different parser. Review parser mode, header limits, connection reuse, decompression, proxy protocol, and error handling as one change.
Who this is for
Use this guide when an agent changes an HTTP reverse proxy, API gateway, request body reader, upload handler, cache, WebSocket upgrade path, service-mesh filter, or framework server. Search for code that parses Content-Length, Transfer-Encoding, chunked bodies, trailers, duplicate headers, or raw sockets. Inspect convenience middleware and proxy defaults as well as direct parser calls.
A request may cross several implementations before application code sees it: CDN, load balancer, ingress, reverse proxy, service mesh, framework, and client library. A patch that is correct for one hop can still create a desynchronization if another hop accepts obsolete line folding, duplicate lengths, malformed chunking, or different whitespace. Include retries and pooled keep-alive connections in the review because leftover bytes can become the next request’s prefix.
Pair this review with the site’s open redirect workflow when a gateway rewrites navigation responses, and with the URL fetcher SSRF workflow when the same proxy accepts caller-selected upstream destinations.
Key takeaways
- Define one message-framing policy for every hop and record the parser and proxy versions that implement it.
- Reject requests containing both
Transfer-EncodingandContent-Lengthunless a narrowly reviewed intermediary rule removes the length before forwarding; never forward the ambiguity. - Reject invalid or conflicting duplicate
Content-Lengthvalues. Equal comma-separated values may be parsed under RFC 9112, but a stricter edge policy can reject duplicates outright. - Treat obsolete line folding, whitespace before the first header, malformed chunk sizes, invalid trailer fields, and unsupported transfer codings as framing errors.
- Close the connection after a framing error so unread bytes cannot be interpreted as a later request.
- Keep strict parser settings enabled. Node.js documents an
insecureHTTPParseroption; an agent patch should not enable permissive parsing to accommodate a broken client without an explicit boundary and compensating test. - Bound request-line, header, body, chunk-extension, trailer, and decompressed sizes, plus read time and concurrent connections.
- Do not let a proxy or framework silently rewrite framing while logs and authorization decisions use a different representation.
- Test the final chain with a recording upstream and harmless byte fixtures. A network block or a
400from the wrong layer is not proof that the intended policy ran. - Log policy codes and parser revisions, not raw request bytes, credentials, or complete attacker-controlled headers.
Sources checked
Four primary public references were fetched live on 2026-10-21:
- RFC 9112: HTTP/1.1
defines HTTP/1.1 message parsing, message-body length precedence, invalid framing behavior, and request-smuggling security considerations. Section 6.3 covers
Transfer-EncodingandContent-Length; Section 11.2 describes parser disagreement as the attack technique. - Node.js HTTP API
documents the HTTP server parser,
maxHeaderSize, connection behavior, and theinsecureHTTPParseroption. The current page was fetched with a 14 October 2026 last-modified date. - NGINX
ngx_http_proxy_moduledocuments reverse-proxy request forwarding, buffering, HTTP version, header, timeout, and request-body directives that affect the hop contract. The live page returned a 20 October 2026 last-modified date. - Apache
mod_proxydocuments reverse and forward proxy modes and warns that an unsecured forward proxy is dangerous. Its current reverse-proxy behavior is a relevant second intermediary to include in integration tests.
These maintained protocol and implementation references support an evergreen engineering review. They do not establish that a particular deployment is vulnerable or safe. The fixture matrix and rollout record below are operational recommendations for the repository and network path under review.
Contract details to verify
Make framing a single boundary decision
Write down how each hop determines the body length and what it does with ambiguity. RFC 9112 gives a precedence order: responses to HEAD, 1xx, 204, and 304 have no body; successful CONNECT responses become tunnels; Transfer-Encoding takes precedence over Content-Length; a final chunked coding determines the body length; invalid lengths are unrecoverable; and a valid Content-Length defines octets when no transfer coding is present. Requests without either field have no body unless the server’s documented protocol permits another mechanism.
Your edge policy can be stricter. For a public API, reject any request with both framing fields, reject duplicate fields unless the service has a tested reason to accept identical values, and reject a transfer coding the next hop does not implement. If an intermediary must normalize a valid request, do so once, before authorization and forwarding, then emit one canonical framing representation. Capture the normalized decision in a request-scoped record so the application and proxy logs can be compared without retaining the payload.
Do not fix malformed input by trimming whitespace, guessing a missing delimiter, or retrying it through a legacy parser. RFC 9112 warns that lenient parsing can create smuggling vulnerabilities when recipients interpret the same bytes differently. A 400 plus connection close is safer than a partially recovered request that leaves unread bytes in a pooled socket.
Align parser modes and versions
Inventory every parser in the path: CDN, load balancer, ingress, reverse proxy, framework, and custom middleware. Record whether it accepts bare LF, whitespace before a header, obsolete line folding, duplicate fields, chunk extensions, and invalid transfer codings. Use strict defaults and make any exception explicit in configuration and tests.
Node’s HTTP API exposes insecureHTTPParser; enabling it changes the acceptance boundary and should be treated as a security-sensitive configuration change. Its maxHeaderSize option is a resource control, not a framing fix. NGINX and Apache directives for proxy version, request buffering, headers, and timeouts likewise shape what bytes reach the next hop. Do not assume a proxy’s default matches the framework behind it. Pin the settings and versions in the review record.
Keep connection reuse safe
When a parser rejects a request, close the connection after sending the error if the protocol and server permit a response. Discarding only the first line can leave a smuggled prefix for the next request on a keep-alive socket. On upstream errors, ensure the proxy does not return the connection to an idle pool with unread bytes. Test cancellation, client disconnects, partial chunk bodies, and upstream connection reuse.
Separate request framing from response framing. A proxy can reject an ambiguous client request and still mishandle an upstream response. Apply the same strict checks to response parsing, especially before caching or forwarding a response to another client. Treat a response with conflicting framing as an upstream protocol error and close or quarantine the connection according to the proxy contract.
Bound resource use without changing semantics
Set limits for request line, total headers, individual field values, body bytes, chunk extensions, trailer fields, decompressed bytes, and time spent waiting for each part. Enforce limits while reading, not after buffering an unbounded body. A limit breach must have one terminal outcome: close or drain according to a documented safe rule, do not hand a partial body to application code, and do not retry through a parser with larger limits.
Keep proxy and application limits compatible. A front end that accepts a larger header block than the back end can parse creates an availability and desynchronization boundary. Include maximum values in deployment configuration and test both just-under and just-over cases. If a route supports streaming, define how cancellation and backpressure interact with the framing decision.
Build a harmless adversarial test matrix
Use a disposable gateway, a recording upstream, and raw byte fixtures that never call a real external service. Assert which layer rejects the bytes, whether the upstream saw zero requests, whether the connection closed, and whether the next valid request on a fresh connection succeeds.
| Fixture | Required observation |
|---|---|
One valid Content-Length body | Exactly one upstream request with the declared octets |
One final Transfer-Encoding: chunked body | Chunks decode once; trailers follow the documented policy |
Both Transfer-Encoding and Content-Length | Edge rejection or one documented normalization; ambiguity never reaches upstream |
Duplicate equal Content-Length fields | The chosen strict or RFC-compatible rule is deterministic and logged |
| Duplicate conflicting lengths | 400, connection close, and zero upstream requests |
| Unsupported transfer coding | Controlled rejection and no parser fallback |
| Malformed chunk size or premature end | Controlled failure; partial body is not promoted |
| Whitespace before the first header or obsolete folding | Rejection or documented discard before application routing |
| Oversized request line, headers, chunk extension, trailer, or body | Limit code, bounded memory, and closed or safely drained connection |
| Rejected request followed by a valid request on the same client socket | Valid request is not interpreted from leftover attacker bytes |
| Upstream response with conflicting framing | Proxy quarantines or closes upstream connection and does not cache or forward it |
insecureHTTPParser or equivalent permissive mode enabled | Configuration gate fails unless an explicit exception and fixtures exist |
Run positive and negative cases through the real proxy and framework configuration. Use a fake upstream that records method, target, headers, and body length. For smuggling-shaped byte sequences, assert no hidden second request reaches it. Do not send probes to public services, shared infrastructure, or another tenant. Keep fixture bytes in a restricted test asset and log only fixture IDs and hashes.
Happy-path operator workflow
- Map the complete request path and list every parser, proxy, cache, and connection pool.
- Write the framing contract and choose the strict edge behavior before changing code.
- Pin parser modes, HTTP versions, header and body limits, buffering, and timeout values in the patch or deployment configuration.
- Reject ambiguous framing before authentication-dependent routing and before forwarding bytes.
- Close or quarantine connections after framing errors and prove unread bytes cannot be reused.
- Test valid fixed-length and chunked requests through the real chain, including trailers and streaming routes.
- Run the adversarial matrix with a recording upstream and assert layer, status, connection state, and zero hidden requests.
- Compare proxy and application logs using sanitized request IDs, framing decisions, parser revisions, and fixture IDs.
- Review response parsing and upstream connection reuse separately from client request parsing.
- Retain the configuration diff, runtime versions, fixture digest, limit values, and promotion decision.
Error-path operator workflow
Map ambiguous framing, invalid length, unsupported coding, malformed chunking, timeout, and limit exhaustion to stable codes such as http_frame_ambiguous, http_content_length_conflict, http_chunk_invalid, http_header_limit, and http_body_timeout. Send a documented 400 or proxy error, close the affected connection, and discard partial state.
Do not normalize a rejected request by stripping headers and forwarding the body, and do not retry through a permissive parser. If a legacy client needs an exception, isolate it at a named boundary with a separate listener, an explicit allowlist, and the same hidden-request tests. If the proxy cannot determine whether an upstream response ended cleanly, close that upstream connection and do not place it back in the pool.
Review state changes on rejection. A malformed upload must not create an object, enqueue a job, advance a cursor, or rotate a token before the framing decision is complete. Keep error responses free of raw header values and request bytes.
Failure modes
The edge rejects Transfer-Encoding plus Content-Length, but the application accepts it. The two-hop contract is still inconsistent. Reject or normalize before forwarding and test the complete chain.
The patch enables a permissive parser to support one old client. That widens the acceptance boundary and may restore parser disagreement. Isolate the client, document the exception, and run the same matrix at both sides.
Only conflicting duplicate lengths are blocked. Equal duplicates and comma-separated forms still need a deliberate rule; different hops must make the same choice.
The proxy returns 400 but keeps the socket alive. Unread bytes can become a later request. Assert connection state and test a valid follow-up request on a new connection.
A body limit is checked after buffering. The worker can exhaust memory before the check. Enforce limits during parsing and make the failure terminal.
A test sees no upstream request because the proxy is unreachable. Use a reachable recording fixture and assert which layer emitted the rejection.
The patch tests only request parsing. Upstream response framing and pooled connections can produce the same desynchronization. Include both directions.
Logs retain raw headers. Smuggling probes can contain credentials, control characters, or sensitive request data. Store codes, IDs, and bounded metadata only.
FAQ
Is rejecting both framing fields enough?
It removes a common ambiguity, but it does not cover duplicate lengths, malformed chunking, line folding, parser version differences, response desynchronization, or connection reuse. Keep the complete hop contract and matrix.
Can a proxy safely normalize the request instead?
Sometimes, when the proxy is the sole framing authority and the normalized bytes and body length are fully determined before forwarding. The rule must be explicit, tested, and identical for all affected paths. Rejection is usually easier to reason about for an untrusted public boundary.
Should all duplicate Content-Length fields be rejected?
A strict edge can reject them. RFC 9112 permits processing a comma-separated list when every value is valid and identical, but the decision must be consistent across every recipient. Document the chosen policy and test equal and conflicting cases.
Why close the connection after a 400?
The parser may have read only part of the bytes. Closing prevents leftovers from being interpreted as a later request on a persistent connection. If a controlled drain is used instead, prove that it consumes exactly the rejected message and cannot expose a second request.
What should a review record contain?
Keep the hop map, parser and proxy versions, framing policy, configuration diff, fixture IDs and hashes, limits, layer and status for each rejection, connection state, upstream request count, and promotion decision. Omit raw request bytes, cookies, authorization headers, and complete hostile fields.
Reader next step
Choose one gateway path changed by the next coding-agent patch. Draw the hops, write the message-framing contract, and add a recording upstream before modifying parser code. Test valid fixed-length and chunked requests, both framing fields, duplicate lengths, malformed chunks, header limits, and a rejected request followed by a fresh valid connection. Block the merge if any hop uses a different parser policy, a permissive parser is enabled without an isolated exception, or a framing error leaves a reusable connection with unread bytes.