CVE-2026-39805 Overview
CVE-2026-39805 is an HTTP request smuggling vulnerability in mtrudel Bandit, an HTTP server for Elixir Plug and Phoenix applications. The flaw stems from inconsistent interpretation of HTTP requests when a client submits two Content-Length headers with different values. Bandit accepts the malformed request and uses the first value, while many upstream proxies pick the last value, creating a desync between the proxy and the origin server. Bandit versions before 1.11.0 are affected. The vulnerability is tracked under [CWE-444] (Inconsistent Interpretation of HTTP Requests).
Critical Impact
Unauthenticated attackers can smuggle requests past edge WAF rules, path-based ACLs, rate limiting, and audit logging when Bandit sits behind a proxy that selects the last Content-Length value.
Affected Products
- mtrudel Bandit versions prior to 1.11.0
- Elixir Plug applications served by vulnerable Bandit releases
- Phoenix Framework deployments using affected Bandit versions
Discovery Timeline
- 2026-05-01 - CVE-2026-39805 published to NVD
- 2026-05-05 - Last updated in NVD database
Technical Details for CVE-2026-39805
Vulnerability Analysis
The vulnerability resides in Elixir.Bandit.Headers.get_content_length/1 within lib/bandit/headers.ex. The function uses List.keyfind/3, which returns only the first matching header tuple. When a request contains two Content-Length headers with different values, Bandit silently accepts the request and reads body bytes according to the first value. The remaining bytes are then treated as a second pipelined request on the same keep-alive connection.
RFC 9112 §6.3 explicitly requires recipients to treat duplicate, conflicting Content-Length headers as an unrecoverable framing error. Bandit's permissive handling violates this requirement.
When Bandit operates behind a reverse proxy or WAF that selects the last Content-Length value, the two parsers disagree on where one request ends and the next begins. An attacker exploits this disagreement to inject a second request that bypasses edge security controls.
Root Cause
The root cause is the use of List.keyfind/3 in get_content_length/1, which returns only the first match instead of detecting duplicate headers. Bandit performs no validation that exactly one Content-Length header is present before parsing the body length.
Attack Vector
The attack vector is network-based and requires no authentication. An attacker crafts an HTTP request containing two Content-Length headers with different values. The request is sent through any intermediary proxy that forwards the malformed request rather than rejecting it. The smuggled portion is interpreted as a separate request by Bandit, bypassing any proxy-enforced controls including authentication checks, rate limits, and audit logging.
@spec get_content_length(Plug.Conn.headers()) ::
{:ok, nil | non_neg_integer()} | {:error, String.t()}
def get_content_length(headers) do
- case get_header(headers, "content-length") do
- nil -> {:ok, nil}
- value -> parse_content_length(value)
+ # We need to special case this because we don't accept multiple content-length headers
+ 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
end
Source: GitHub Bandit Commit f2ca636. The patch replaces List.keyfind/3 with Enum.filter/2 and explicitly rejects requests carrying multiple Content-Length headers, returning a framing error consistent with RFC 9112 §6.3.
Detection Methods for CVE-2026-39805
Indicators of Compromise
- HTTP requests received by Bandit with two or more Content-Length headers in a single request
- Unexpected pipelined requests on keep-alive connections that did not traverse the front-end proxy log
- Origin server access logs showing requests with paths or methods that should have been blocked at the edge WAF
- Discrepancies between proxy access logs and Bandit access logs for the same client connection
Detection Strategies
- Inspect raw HTTP headers at the proxy layer for duplicate Content-Length occurrences and alert on any match
- Compare request counts and URIs between the upstream proxy and the Bandit application logs to identify smuggled requests
- Deploy WAF signatures that reject any request containing more than one Content-Length header before forwarding upstream
- Review Phoenix or Plug telemetry for sudden bursts of requests on a single keep-alive connection from the same source IP
Monitoring Recommendations
- Forward Bandit access logs to a centralized logging or SIEM platform and correlate with edge proxy logs by connection identifier
- Alert on parser errors from upstream proxies that indicate framing mismatches
- Track Bandit version inventory across deployments to confirm patched releases are running
How to Mitigate CVE-2026-39805
Immediate Actions Required
- Upgrade mtrudel Bandit to version 1.11.0 or later across all Elixir and Phoenix deployments
- Audit the upstream proxy or load balancer to confirm it rejects requests with duplicate Content-Length headers
- Review recent access logs for evidence of smuggled requests bypassing edge controls
Patch Information
The fix is committed in GitHub commit f2ca636 and published in Bandit 1.11.0. Additional context is available in the GitHub Security Advisory GHSA-c67r-gc9j-2qf7 and the CNA advisory from the Erlang Ecosystem Foundation. The patched function uses Enum.filter/2 to detect multiple Content-Length headers and returns {:error, "invalid content-length header (RFC9112§6.3)"} when more than one is present.
Workarounds
- Configure the upstream proxy or WAF to reject any inbound request with multiple Content-Length headers before it reaches Bandit
- Disable HTTP keep-alive at the proxy boundary to limit smuggling impact while patching is in progress
- Add a custom Plug at the application entry point that inspects raw headers and returns 400 when duplicate Content-Length values are detected
# Update Bandit dependency in mix.exs to the patched release
# {:bandit, "~> 1.11.0"}
mix deps.update bandit
mix deps.get
mix compile
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


