- Understanding HTTP Trailers: The “Why” and “How”
- The Danger of “Trailer Merging” and State Desynchronization
- Upstream Trailer Merging: Full Request Smuggling
- Anomalous Trailer Parsing
- Impact and Affected Implementations
- Practical Defenses and Mitigations
- Conclusion
- Reference
This is my journey on understand HTTP Trailer into more depth. To start lets go back when HTTP/1.1 introduced chunked transfer encoding, when it did, it brought along a relatively obscure feature known as “HTTP trailers.” This mechanism allows clients or servers to append extra header fields after the main message body has been transmitted. While mostly ignored by modern standard web applications, trailers are still deeply baked into HTTP standards. Unsurprisingly, because they are so rarely used and tested, different web servers, proxies, and libraries handle them with wildly varying logic.
In this post I dive into the security fallout of these parsing inconsistencies. Prompted by a fascinating loophole in RFC 9112 regarding header merging, this research uncovered a massive attack surface. By exploiting how different HTTP components interpret trailers, attackers can execute novel HTTP Request and Header Smuggling attacks. In modern web architectures where a single request might traverse a CDN, a WAF, an ingress controller, and multiple microservices before reaching its destination even a single parsing disagreement in that chain can completely compromise the application’s security posture.
Understanding HTTP Trailers: The “Why” and “How”
Trailers exist to solve a specific, practical problem in streaming data: sometimes you need to send metadata that you simply don’t have until the entire body is processed. Common examples include cryptographic signatures, data integrity checksums, or post-processing status codes.
Without trailers, if a server wants to send a SHA-256 hash of a dynamically generated file in the HTTP headers, it must buffer the entire file in memory, calculate the hash, send the headers, and then send the body. This is highly inefficient and breaks streaming. Trailers let the sender transmit the body as it’s generated and append the metadata at the very end. This mechanism is heavily utilized by modern RPC frameworks like gRPC to transmit final call status codes without blocking the data stream.
In HTTP/1.1, you can only send trailers if you use Transfer-Encoding: chunked. The structure looks like this:
POST / HTTP/1.1 \r\n <- REQUEST LINE + HEADERSHost: localhost \r\nTransfer-Encoding: chunked \r\nTrailer: trailing, digest \r\n\r\n4 \r\n <- CHUNKED BODYbody \r\n0 \r\n <- ZERO-SIZE CHUNK (END OF BODY)Trailing: field \r\n <- TRAILERS SECTIONDigest: SHA-256=sha256trailervalue \r\n\r\n <- REQUEST TERMINATOR
Here, the zero-size chunk (0\r\n) indicates the payload is finished, immediately followed by the trailer fields, and finally capped off with an empty line (\r\n).
While HTTP/2 and HTTP/3 did away with chunked encoding entirely, they still fully support trailers. In these newer protocols, trailers are attached to a final HEADERS frame carrying the END_STREAM flag, sent on the same stream after all DATA frames. Because intermediaries frequently translate requests from HTTP/2 at the edge back down to HTTP/1.1 for internal microservices, trailer translation and processing become a massive source of ambiguity.
The Danger of “Trailer Merging” and State Desynchronization
The core of this vulnerability lies in RFC 9112 (§7.1.2). The specification states that when an intermediary strips chunked encoding from a message (perhaps to buffer it or forward it as a standard request), it can choose to discard the trailers, forward them as-is, or merge them directly into the primary header section.
The authors of the RFC explicitly warned developers not to merge security-sensitive headers such as Host, Content-Length, Transfer-Encoding, or authentication tokens because their evaluation is critical for routing and framing. Safe merging is restricted to fields like Digest that don’t alter request boundaries or application state.
Unfortunately, many implementations fail to enforce this safety warning. When a software component blindly takes everything in the trailer section and appends it to the main headers, it creates a fatal desynchronization between different layers of the infrastructure. The front-end proxy makes security decisions based on the initial headers, but the backend processes the request based on the merged headers. This discrepancy is the root cause of Header Smuggling via Trailer Merge.
Downstream Trailer Merging: Bypassing Front-End Security
If a backend server (downstream) improperly merges trailers but the front-end proxy does not, attackers can bypass security filters. The proxy inspects the initial headers, deems the request safe, and passes it along. The backend then unpacks the trailers, merges them into the main headers, and overwrites the originally validated data.
Scenario 1: Access Control and WAF Bypass
Let’s look at a practical scenario where a backend service relies on the X-Forwarded-For header to verify a user’s origin or privilege level.
Consider the following simple Go handler. It checks the incoming request for a specific IP address and grants access only if the user is connecting from 127.0.0.1:
func processRequest(w http.ResponseWriter, r *http.Request) { clientIP := r.Header.Get("X-Forwarded-For") if clientIP == "127.0.0.1" { w.Write([]byte("Welcome, localhost!")) return } http.Error(w, "Access Denied", http.StatusForbidden)}
To protect this internal logic from external spoofing, the application is deployed behind an HAProxy instance. The proxy acts as a firewall, configured to immediately drop any external requests that attempt to manually inject the X-Forwarded-For header:
http-request deny if { req.fhdr(x-forwarded-for) -m found }
Under normal circumstances, if an attacker tries to inject this header in a standard HTTP request, the proxy blocks it outright:
Standard Spoofing Attempt (Blocked by Proxy):
GET / HTTP/1.1\r\nHost: localhost\r\nX-Forwarded-For: 127.0.0.1\r\n\r\n
Proxy Response:
HTTP/1.1 403 Forbidden\r\nContent-Length: 93\r\nCache-Control: no-cache\r\nContent-Type: text/html\r\n\r\n<html><body><h1>403 Forbidden</h1>Request forbidden by administrative rules.</body></html>
The Trailer Merge Exploit
This security architecture falls apart due to a parser discrepancy. HAProxy strictly inspects the primary header block and ignores trailers, while the backend application automatically merges any trailer fields into the main headers before the application logic evaluates them.
An attacker can easily circumvent the Access Control List (ACL) by hiding their payload in the trailer section using chunked transfer encoding:
Smuggled Request (Bypasses Proxy):
GET / HTTP/1.1\r\nHost: localhost\r\nTransfer-Encoding: chunked\r\n\r\n0\r\nX-Forwarded-For: 127.0.0.1\r\n\r\n
Backend Response:
HTTP/1.1 200 OK\r\nServer: fasthttp\r\nDate: Mon, 27 Oct 2025 00:22:27 GMT\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 19\r\n\r\nWelcome, localhost!
Because the malicious X-Forwarded-For header was located in the trailer, HAProxy never saw it during its initial validation. Once it reached the backend, the merging process promoted it to a standard header, tricking the Go application into granting full access.
Scenario 2: Host Header Spoofing
Similarly, if a backend utilizes the Host header to generate dynamic content such as absolute URLs in password reset links, OAuth redirects, or caching keys an attacker can override the legitimate Host header. By placing Host: attacker-server.com in the trailer, the backend will merge it. This bypasses the front-end proxy’s strict virtual host routing (which relied on the original, safe Host header), and ultimately poisons the reset link sent to a victim, directing them straight to an attacker-controlled server.
Scenario 3: Web Cache Poisoning
If a caching layer sits between the front-end proxy and the backend server, trailer merging can be weaponized to achieve devastating Web Cache Poisoning. The caching mechanism evaluates the request based on the initial headers to form its “cache key.” Meanwhile, the backend merges a malicious trailer (e.g., overriding a X-Original-URL or a custom framework routing header) and serves a completely different, attacker-controlled response. The cache stores this malicious response under the safe, legitimate URL, serving it to all subsequent innocent users who request the same resource.
Upstream Trailer Merging: Full Request Smuggling
The risk escalates severely if an upstream component (like a client-facing reverse proxy) performs the unsafe merge. In this scenario, attackers can manipulate framing headers like Content-Length or Transfer-Encoding, confusing the backend about where one request ends and another begins on a shared TCP Keep-Alive connection.
- The attacker sends a request with
Content-Length: 0hidden in the trailers. - The proxy parses the initial headers, validates them, and processes the full body. Then, it unsafely merges the trailers, silently overwriting the original framing headers.
- The proxy forwards this newly merged, corrupted state to the backend.
- The backend sees
Content-Length: 0, assumes the current request has no body, and immediately processes the remaining bytes (which were part of the original request’s body) as a completely new, “smuggled” HTTP request.
This smuggled request will then be attached to the next legitimate user’s request traversing that connection, allowing the attacker to steal the victim’s session cookies or serve them arbitrary data.
Case Study: lighttpd TR.MRG Vulnerability
In lighttpd 1.4.x, the server was found to merge trailers after de-chunking a request. Crucially, it allowed this process to overwrite the Content-Length header before passing the request downstream. On its own, the developers of lighttpd mitigated this by forcing the proxy to append a Connection: close header whenever framing headers were tampered with, intentionally killing the connection and preventing smuggling.
However, security vulnerabilities rarely exist in a vacuum; they chain together. Many downstream web servers incorrectly parse the Connection header. RFC 9110 states that Connection can hold a comma-separated list of tokens (e.g., Connection: close, te). If close is anywhere in that list, the connection must be terminated. Because lighttpd appended multiple tokens, flawed backend parsers (which were simply doing exact string matching for "close") missed the close directive entirely. They kept the connection alive, transforming a mitigated header manipulation bug into a critical HTTP Request Smuggling exploit.
Anomalous Trailer Parsing
Merging isn’t the only issue. Basic parsing discrepancies and logic flaws in how edge cases are handled between proxies and backends also open the door to smuggling.
- Blind Spots (Unparsed Trailers): Software like Eventlet completely skipped reading trailers. Instead of implementing a full HTTP state machine, its parser stopped immediately after reading the zero-chunk. If a front-end proxy successfully parses and forwards the full request (trailers included) down to Eventlet, Eventlet treats the leftover, unread trailer bytes as the start of a brand new HTTP request on the socket. This “socket poisoning” directly leads to request smuggling.
- Premature Termination: The
http4slibrary had a bug where its parser would fatally crash or terminate the request parsing loop if it encountered a trailer line missing a standard colon. Crucially, it failed to wait for the final blank line (\r\n\r\n) that marks the end of an HTTP message. By strategically feeding it malformed trailers, an attacker could forcehttp4sto abandon processing the current request, tricking it into executing the subsequent payload (hidden at the end of the malformed trailer) as a smuggled request. - Hide-Merge-Smuggle: Some high-performance parsers optimize for speed by mistakenly allowing illegal characters in header names (e.g., blindly reading bytes until the first colon is found, no matter what precedes it). Attackers can inject a newline and start a fully formed smuggled request inside the trailer key name. A strict front-end proxy sees a single request with a weird, multiline trailer. A lenient backend, however, sees the injected newline boundary, splits the stream, and executes the hidden request.
Impact and Affected Implementations
Extensive testing against over 70 open-source tools revealed widespread vulnerabilities. The results proved that HTTP trailers are a neglected corner of web security. Several major libraries and servers were susceptible to either header smuggling or full request smuggling due to these oversights:
- Fasthttp: Header Smuggling (Partially Patched)
- Lighttpd (1.4.x): Header Smuggling (CVE-2025-12642)
- cpp-httplib & boost.org/beast: Header Smuggling (CVE-2026-63628)
- http4s: Request Smuggling via early termination (CVE-2025-58068)
- Eventlet: Request Smuggling via unparsed trailers (CVE-2026-69822)
- Other affected platforms: Eclipse Glassfish, Cheroot, Libevent, PHP Built-in Server.
Many of these libraries power massive enterprise applications, API gateways, and internal microservices. Patching them often requires fundamentally rewriting how their HTTP state machines process the end of a request stream, which takes significant development effort.
Practical Defenses and Mitigations
Defending against Trailer Smuggling requires a defense-in-depth approach, primarily focused on strict protocol validation and reducing attack surface.
- Disable Trailers if Unused: The vast majority of standard web applications do not need HTTP trailers. If you are not actively utilizing gRPC or specific streaming checksums, configure your front-end reverse proxies (Nginx, HAProxy, Envoy) to actively strip or reject requests containing the
Trailerheader or chunked trailers. - Normalize HTTP Traffic: Ensure that your WAFs and edge proxies are configured to strictly enforce RFC compliance. Proxies should normalize requests by explicitly dropping malformed headers, normalizing line endings (rejecting bare
\n), and rejecting headers with illegal characters before forwarding them. - HTTP/2 Upgrades: While HTTP/2 still supports trailers, upgrading internal proxy-to-backend communication to HTTP/2 (instead of downgrading back to HTTP/1.1) eliminates the ambiguity of
Transfer-Encoding: chunkedframing, closing off several vectors of request smuggling related to chunk boundary confusion. - Audit Trailer Handling: If trailers must be used, audit your backend frameworks to ensure they do not merge trailers into the primary headers array by default. Trailers should be exposed to the application through a distinct, separate API object (e.g.,
request.trailers.get()) to prevent accidental WAF bypasses and logic poisoning.
Conclusion
This research highlights a recurring theme in protocol security: legacy features, even when largely forgotten by the modern web, remain active in the underlying codebases of our infrastructure. When complex specifications leave room for interpretation such as optionally merging headers implementations will inevitably diverge.
HTTP trailers might be an afterthought for most developers, but as these parsing discrepancies demonstrate, they provide a powerful, under-the-radar vector for manipulating web traffic. As web architectures grow increasingly complex and reliant on multiple proxy layers, understanding and securing the edges of our foundational protocols is more critical than ever.