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-Encoding and Content-Length unless a narrowly reviewed intermediary rule removes the length before forwarding; never forward the ambiguity.
  • Reject invalid or conflicting duplicate Content-Length values. 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 insecureHTTPParser option; 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 400 from 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-Encoding and Content-Length; Section 11.2 describes parser disagreement as the attack technique.
  • Node.js HTTP API documents the HTTP server parser, maxHeaderSize, connection behavior, and the insecureHTTPParser option. The current page was fetched with a 14 October 2026 last-modified date.
  • NGINX ngx_http_proxy_module documents 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_proxy documents 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.

FixtureRequired observation
One valid Content-Length bodyExactly one upstream request with the declared octets
One final Transfer-Encoding: chunked bodyChunks decode once; trailers follow the documented policy
Both Transfer-Encoding and Content-LengthEdge rejection or one documented normalization; ambiguity never reaches upstream
Duplicate equal Content-Length fieldsThe chosen strict or RFC-compatible rule is deterministic and logged
Duplicate conflicting lengths400, connection close, and zero upstream requests
Unsupported transfer codingControlled rejection and no parser fallback
Malformed chunk size or premature endControlled failure; partial body is not promoted
Whitespace before the first header or obsolete foldingRejection or documented discard before application routing
Oversized request line, headers, chunk extension, trailer, or bodyLimit code, bounded memory, and closed or safely drained connection
Rejected request followed by a valid request on the same client socketValid request is not interpreted from leftover attacker bytes
Upstream response with conflicting framingProxy quarantines or closes upstream connection and does not cache or forward it
insecureHTTPParser or equivalent permissive mode enabledConfiguration 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

  1. Map the complete request path and list every parser, proxy, cache, and connection pool.
  2. Write the framing contract and choose the strict edge behavior before changing code.
  3. Pin parser modes, HTTP versions, header and body limits, buffering, and timeout values in the patch or deployment configuration.
  4. Reject ambiguous framing before authentication-dependent routing and before forwarding bytes.
  5. Close or quarantine connections after framing errors and prove unread bytes cannot be reused.
  6. Test valid fixed-length and chunked requests through the real chain, including trailers and streaming routes.
  7. Run the adversarial matrix with a recording upstream and assert layer, status, connection state, and zero hidden requests.
  8. Compare proxy and application logs using sanitized request IDs, framing decisions, parser revisions, and fixture IDs.
  9. Review response parsing and upstream connection reuse separately from client request parsing.
  10. 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.