EEF-CVE-2026-94194
Mint HTTP/1 client applies chunked framing when chunked is not the final transfer coding, enabling response smuggling through intermediaries
Quick fix
EEF-CVE-2026-94194 — mint: upgrade to the fixed version with the command below.
mix deps.update mintDetails
## Summary
Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling') vulnerability in elixir-mint mint allows a malicious HTTP/1 server to desynchronize an intermediary and the Mint client on a pooled connection, poisoning the responses to subsequent requests that share the connection.
`message_body/1` in `lib/mint/http1.ex` selects chunked framing when `chunked` is the first coding listed in a response's `Transfer-Encoding` fields. RFC 9112 section 6.3 applies chunked framing only when `chunked` is the final coding, and otherwise reads the body until the server closes the connection. For a response such as `Transfer-Encoding: chunked, gzip`, an intermediary that follows the RFC treats every byte up to the close as the body, while Mint ends the body at the zero-length chunk and parses the remaining bytes as the response to the next request on the connection.
Mint also keeps the connection open after an HTTP/1.0 response, final or 1xx, that carries `Transfer-Encoding` and `Connection: keep-alive`. RFC 9112 section 6.1 requires treating the framing of such a message as faulty and closing the connection after it, so bytes after its chunked body are parsed as the response to the next request in the same way.
This issue affects mint: from 0.1.0 before 1.11.0.
## Details
**1. Coding list.** `store_header/3` in `lib/mint/http1.ex` tokenizes each `Transfer-Encoding` field with `Mint.HTTP1.Parse.transfer_encoding_header/1` and appends the codings to `request.transfer_encoding` in the order received.
**2. Framing decision.** `message_body/1` returns chunked framing when `List.first(request.transfer_encoding)` is `"chunked"`. The only other `Transfer-Encoding` check rejects a response that also carries `Content-Length`, so `chunked, gzip` on its own is framed as chunked.
**3. Leftover bytes.** `decode_body/5` ends the body at the zero-length chunk and its trailer section, and `next_request/3` keeps the rest of the data in `conn.buffer` or decodes it immediately as the next queued response. An RFC 9112 intermediary frames the same response as ending at connection close, so bytes it forwarded as body of the first response become the response to the next request on the Mint connection, and that request's real response is left in the buffer for the one after it.
**4. HTTP/1.0 keep-alive.** `request_done/2` keeps an HTTP/1.0 connection open when the response has `Connection: keep-alive`, without checking for `Transfer-Encoding`, and `decode_body(:informational, ...)` resets the framing state of a 1xx response and keeps parsing. An HTTP/1.0 response with `Transfer-Encoding: chunked` and `Connection: keep-alive` therefore leaves the connection open after its chunked body, and the following bytes are decoded as the next queued response, although RFC 9112 section 6.1 requires closing the connection after such a message.
## Proof of concept
1. Start a loopback TCP server that answers the first request with `HTTP/1.1 200 OK` and `Transfer-Encoding: chunked, gzip`, the chunked body `5\r\nLEGIT\r\n0\r\n\r\n`, and directly after it the bytes `HTTP/1.1 200 OK\r\nContent-Length: 8\r\n\r\nSMUGGLED`. 2. Connect with `Mint.HTTP1` and send a request. Mint returns `LEGIT` as the complete body, keeps the connection open, and holds the second response in `conn.buffer`. 3. Send a second request on the same connection and have the server answer it with a real response. Mint returns `SMUGGLED` as the response to the second request and keeps the real one in the buffer. 4. Pipelining both requests before the server answers gives the same result from a single `Mint.HTTP1.stream/2` call.
## Impact
A malicious or attacker-influenced HTTP/1 origin behind an intermediary that follows RFC 9112 framing can make the intermediary and the Mint client disagree on where a response ends. On a pooled keep-alive connection, bytes the origin appends to one response become the response to the next request sharing the connection, and the real responses shift to later requests.
## Configurations
Exploitation requires an HTTP/1 intermediary (proxy, load balancer, or gateway) between the Mint client and the attacker-influenced origin that frames a response whose final transfer coding is not `chunked` as ending at connection close and forwards its `Transfer-Encoding` header to the client unchanged, and HTTP/1 connections between the client and the intermediary that are reused across requests. Intermediaries that reject such a header or re-frame the body do not create the disagreement. Mint clients that connect to the origin directly, or that don't reuse connections, aren't exposed to response-queue poisoning.
Are you affected?
Enter the version of the package you're using.
Affected packages
References
- https://github.com/elixir-mint/mint/security/advisories/GHSA-gvrc-75rc-7gj9[ADVISORY]
- https://cna.erlef.org/cves/CVE-2026-94194.html[WEB]
- https://github.com/elixir-mint/mint/commit/60089586ec7adc9fddb09f69a2f5919ba9ac7f33[WEB]
- https://github.com/elixir-mint/mint/commit/2ec8b696b5475ecbdaa87c0098957bca339e17c0[FIX]
- https://hex.pm/packages/mint[PACKAGE]