May 7, 2026·7 min read·28 visits
Bandit < 1.11.0 accepts duplicate Content-Length headers and processes only the first one, violating RFC 9112. When deployed behind certain reverse proxies, this allows attackers to smuggle hidden HTTP requests to bypass frontend access controls.
The Bandit HTTP server for Elixir versions prior to 1.11.0 fails to correctly process requests containing multiple Content-Length headers. This inconsistent interpretation creates a CL.CL HTTP request smuggling vulnerability when Bandit is deployed behind a reverse proxy that parses the headers differently. Attackers exploit this desynchronization to smuggle secondary HTTP requests past edge security controls.
Bandit is a prevalent HTTP server written in Elixir, often utilized as a robust backend for Phoenix applications. Versions prior to 1.11.0 contain a critical protocol parsing flaw classified under CWE-444: Inconsistent Interpretation of HTTP Requests. The server improperly handles incoming HTTP requests that present duplicate Content-Length headers, violating the strict parsing requirements outlined in RFC 9112.
The vulnerability surfaces specifically in distributed architectures where Bandit is situated behind a frontend reverse proxy, such as older versions of Nginx, HAProxy, or specific cloud load balancers. When an attacker submits an HTTP request containing multiple Content-Length headers, the frontend proxy and the Bandit backend must agree on the exact boundaries of the request body. If the parsing logic diverges, the backend and frontend lose synchronization over the TCP stream.
In this specific case, the desynchronization manifests as a CL.CL request smuggling vector. Bandit relies on a "first-wins" logic for extracting the payload length, whereas a vulnerable frontend proxy might utilize a "last-wins" logic. This discrepancy allows an attacker to dictate two different request lengths simultaneously.
The resulting impact is the ability to smuggle arbitrary HTTP requests within the apparent body of a primary request. The frontend proxy inspects only the primary request, allowing the smuggled payload to bypass Web Application Firewalls (WAFs), rate-limiting mechanisms, and path-based access control lists (ACLs) entirely.
The root cause resides in the header extraction logic implemented within lib/bandit/headers.ex. Specifically, the function 'Elixir.Bandit.Headers':get_content_length/1 is responsible for determining the size of the incoming HTTP request body. This function processes the parsed header list to extract the standard Content-Length integer value.
The vulnerability is introduced through the use of the Elixir standard library function List.keyfind/3. In Elixir, HTTP headers are typically represented as a list of two-element tuples, structured as {String.t(), String.t()}. List.keyfind/3 operates by iterating over the list and returning the very first tuple that matches the specified key.
When an attacker transmits a request containing two separate Content-Length headers, List.keyfind/3 successfully locates the first instance and halts its search. Bandit subsequently utilizes this initial value to determine the boundaries of the request body. The server entirely ignores the presence of the secondary Content-Length header.
RFC 9112 Section 6.3 explicitly mandates that a recipient must reject requests containing multiple Content-Length headers with varying values, typically by issuing a 400 Bad Request response. By silently accepting the first value and continuing execution, Bandit inadvertently facilitates the exact framing error required for request smuggling.
The vulnerable implementation blindly trusts the first matching header tuple without validating the uniqueness of the key across the entire header list. The execution flow extracts the initial value, parses it as an integer, and proceeds to read from the socket buffer.
# Vulnerable logic (simplified representation)
def get_content_length(headers) do
case List.keyfind(headers, "content-length", 0) do
{_, value} -> parse_content_length(value)
nil -> {:ok, nil}
end
endThe fix, introduced in commit f2ca636eb6df385219957e8934e9fc6efa1630d1, completely replaces the List.keyfind/3 approach. The patched code utilizes Enum.filter/2 to explicitly collect every instance of the content-length key present in the header list.
# Patched logic in lib/bandit/headers.ex
def get_content_length(headers) do
case Enum.filter(headers, &(elem(&1, 0) == "content-length")) do
[] -> {:ok, nil}
[{"content-length", value}] -> parse_content_length(value)
_ -> {:error, "invalid content-length header (RFC9112§6.3)"}
end
endThis structural change enforces strict protocol compliance. The case statement validates the output of the filter operation. If the resulting list contains exactly one tuple, the value is parsed. If the list contains multiple tuples, the execution drops to the catch-all _ branch, actively returning an error tuple. This error state triggers an immediate 400 Bad Request response, neutralizing the smuggling vector.
Exploiting this vulnerability strictly requires a frontend proxy that prioritizes the last Content-Length header or forwards duplicate headers unaltered to the backend. The attacker targets the application by crafting a monolithic HTTP payload that embeds a secondary, malicious request within the body boundaries defined by the frontend.
The attacker constructs a request with two conflicting length declarations. For example, the first header asserts Content-Length: 4, while the second header asserts Content-Length: 50. The body of the request contains the 4 legitimate bytes, immediately followed by the smuggled 46-byte HTTP request targeting a restricted endpoint.
The frontend proxy, adhering to its "last-wins" logic, processes the Content-Length: 50 header. It consumes the entire 50-byte payload from the attacker and forwards the monolithic block over a persistent keep-alive connection to Bandit. The proxy evaluates only the primary request headers against its security rules.
Bandit receives the transmission and parses the headers, terminating its search at Content-Length: 4. It reads exactly 4 bytes from the socket buffer to complete the first request. Because the connection utilizes keep-alive, Bandit immediately attempts to parse the next request from the remaining 46 bytes sitting in the buffer. The smuggled request is executed with the internal trust level of the proxy connection.
The security impact of a successful HTTP request smuggling attack primarily involves the bypass of security perimeter controls. Frontend reverse proxies typically enforce path-based access control, block malicious query parameters, and handle authentication validation. By smuggling a request, an attacker routes traffic directly to the backend Elixir application without triggering any of these edge inspections.
This bypass enables direct access to internal administrative endpoints, internal APIs, or unprotected administrative panels that rely on frontend routing restrictions for security. Depending on the backend application logic, this can lead to credential extraction, data manipulation, or unauthorized administrative actions.
The CVSS v4.0 score is calculated as 6.3 (Medium), utilizing the vector CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N. The Attack Requirements (AT) metric is classified as Present (P) because the vulnerability cannot be exploited in isolation; it strictly requires a misconfigured or vulnerable frontend proxy to facilitate the payload delivery.
The Exploit Prediction Scoring System (EPSS) assigns a probability score of 0.00017, placing it in the 4.03 percentile. This low score accurately reflects the complex environmental prerequisites necessary for successful exploitation. Widespread, automated exploitation is unlikely without specific target profiling.
The primary and most effective remediation is upgrading the bandit dependency to version 1.11.0 or later. This patch fundamentally corrects the header parsing logic, ensuring that Bandit rejects malformed requests at the protocol level. Development teams must update their mix.exs files and execute mix deps.get to apply the upstream fix.
In environments where immediate backend patching is restricted, security engineers must implement strict header normalization at the frontend reverse proxy or WAF layer. Proxies must be configured to actively drop requests containing duplicate Content-Length headers, or alternatively, normalize the request to contain a single, validated length declaration before forwarding traffic to the backend.
Security operations teams should deploy detection rules to identify exploitation attempts. Network intrusion detection systems can flag inbound HTTP requests bearing multiple Content-Length headers. Additionally, backend application logs should be monitored for the specific error string invalid content-length header (RFC9112§6.3), which indicates that the patched Bandit server is actively terminating smuggled payloads.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
bandit mtrudel | < 1.11.0 | 1.11.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-444 |
| Attack Vector | Network |
| CVSS v4.0 | 6.3 |
| EPSS Score | 0.00017 (4.03%) |
| Impact | Security Control Bypass |
| Exploit Status | Proof of Concept |
| KEV Status | Not Listed |
Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')
CVE-2026-63349 is a critical privilege-dropping bypass vulnerability in the AnyIO asynchronous framework (versions 4.14.0 and 4.14.1) on POSIX platforms. Due to a variable assignment typo, supplementary groups specified by the developer are not correctly propagated to the execution backend, resulting in subprocesses retaining the parent process's elevated supplementary group permissions.
CVE-2026-63406 is an information disclosure vulnerability in AnyCable-go prior to version 1.6.15. The built-in telemetry client is enabled by default with a hardcoded public authentication token ('secret'). This client digests highly sensitive configuration parameters and command-line arguments, including JWT secrets and RPC secrets, into a stable SHA-256 fingerprint. This fingerprint is sent over public networks, exposing those administrative secrets to offline dictionary and brute-force attacks if intercepted.
CVE-2026-84992 is a Cross-Site Scripting (XSS) vulnerability affecting md-editor-v3 before version 6.5.4. It occurs because the fenced-code block language parser directly interpolates unescaped language metadata into unquoted HTML attributes inside the custom rendering callback. This bypasses the built-in XSSPlugin which runs during the parsing phase, before rendering.
CVE-2026-81505 is a high-severity Broken Object Level Authorization (BOLA) / Insecure Direct Object Reference (IDOR) vulnerability in Convoy, a cloud-native webhooks gateway. In affected versions prior to 26.6.8, the single-item Source retrieval API endpoint authorizes project access but fails to confirm if the requested Source belongs to that specific project. This logical flaw allows authenticated users or project-scoped API key holders to bypass tenant isolation boundaries and retrieve unredacted, plaintext message broker credentials for Apache Kafka, Amazon SQS, RabbitMQ, and Google Cloud Pub/Sub belonging to other tenants. This issue is fully patched in version 26.6.8.
CVE-2026-77339 is a critical security vulnerability in Process Compose before version 1.120.0. The Model Context Protocol (MCP) Server-Sent Events (SSE) listener transport subsystem fails to validate the HTTP Host and Origin headers, and does not enforce authentication. This omissions expose local loopback listeners to DNS rebinding attacks orchestrated by malicious remote websites visited by developers, enabling unauthorized process control and arbitrary command execution.
CVE-2026-77301 is a critical uncontrolled resource allocation vulnerability in the popular Node.js library adm-zip (versions prior to 0.6.1). During ZIP decompression of asynchronous entries, the library trusts the uncompressed size metadata declared in the central directory headers. Because Node.js's streaming zlib API completely ignores the maxOutputLength configuration, a crafted ZIP archive (decompression bomb) causes the application to continually allocate resident memory buffers on the heap without limits, causing rapid memory exhaustion and a process-level Out-of-Memory (OOM) crash.