Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

CVE-2026-58229 — Mint: unbounded HTTP/1 response-header and chunked-trailer accumulation

  • Advisory: GHSA-qrfr-wh4c-3qhw · CVE-2026-58229
  • Affected: mint (hex) ≥ 0.1.0, < 1.9.2 · Fixed: 1.9.2
  • Severity: High · CWE-770 (Allocation of Resources Without Limits)
  • Status: publicly disclosed and fixed. Reported by Pig-Tail through coordinated disclosure.

Summary

Mint's HTTP/1 response parser accumulates every parsed response header — and every chunked-trailer header — into an unbounded per-request list that is only cleared when the terminating blank line arrives. A remote server the Mint client can be induced to talk to (directly, via SSRF, via an auto-followed redirect, or via a MITM) can stream complete header or trailer lines forever, growing connection state on the client until the BEAM node is killed by the OS out-of-memory handler.

Root cause

1. Unbounded response-header accumulator. Mint.HTTP1.decode_headers/5 (lib/mint/http1.ex) walks the header section by calling the line parser and prepending each {name, value} tuple to a growing list. When the incoming TCP segment ends mid-section, that list is stashed in request.headers_buffer and the trailing bytes in conn.buffer, ready to resume on the next Mint.HTTP1.stream/2 call. The section is released only when the parser returns :eof — the terminating blank line — and nothing in between caps either the number of headers or the byte size of the section.

2. The same pattern for chunked trailers. Mint.HTTP1.decode_trailer_headers/4 reuses the accumulator-plus-headers_buffer shape for HTTP/1.1 trailers. After a zero-size chunk the parser enters trailer mode, and every trailer line is prepended to a list held on the request across stream/2 calls — again terminated only by :eof.

3. The underlying parser has no built-in cap. Mint.HTTP1.Response.decode_header/1 (lib/mint/http1/response.ex) calls:

:erlang.decode_packet(:httph_bin, binary, [])

with an empty option list, so packet_size and line_length both default to 0 — unlimited. No layer between the socket and the accumulator constrains the section.

4. Denial of service. A malicious server sends the status line (or, for the trailer variant, Transfer-Encoding: chunked plus a small chunk followed by 0\r\n) and then streams complete header or trailer lines indefinitely, never writing the closing blank line. The client's conn state grows on every stream/2 call until the node is OOM-killed.

Impact

Any application using Mint as an HTTP/1 client is affected whenever it can be induced to talk to an attacker-controlled server — directly, through an auto-followed redirect, through SSRF, or through a network MITM. A single connection is enough to exhaust the node's memory and terminate the whole application process.

Note that Mint sits under Finch, and therefore under Req and Tesla's Finch adapter, so the reachable surface is considerably wider than direct Mint users.

PoC

Two halves, because the attack is a client/server interaction.

The attacker origin — poc/malicious_server.py

Opens a header (or chunked-trailer) section and streams complete lines forever without ever writing the terminator. Python stdlib only, binds loopback, serves one connection by default.

python poc/malicious_server.py --mode header      # or --mode trailer
python poc/malicious_server.py --selftest         # local reader, reports throughput

--selftest drives the server with a plain socket reader and measures what it emits:

[=] mode                      : header
[=] read in 2.0s                : 314.6 MB (157.2 MB/s)
[=] header section closed     : no  (blank line never sent)
[=] mode                      : trailer
[=] read in 2.0s                : 269.3 MB (134.6 MB/s)
[=] chunked-trailer section closed: no  (blank line never sent)

Both figures are a single response: ~3.9 million header lines in two seconds, none of which Mint may discard before the terminator it never receives.

The victim client — poc/mint_client.exs

An ordinary Mint.HTTP1.stream/2 receive loop — the one from Mint's own documentation — printing the process heap as it climbs.

elixir poc/mint_client.exs                        # defaults to 127.0.0.1:8099

It aborts at a 1 GB ceiling rather than riding the node into the OOM killer. On Mint ≥ 1.9.2 the connection errors out once the section exceeds the new cap, and the loop prints that error instead of climbing — which makes the same script a patch check.

What I ran, and what I did not. The server half and its self-test were executed here: the throughput figures above are real output. The Elixir half was not run — this machine has no BEAM installed — so it is written against Mint's documented stream/2 API but not empirically verified. The client-side growth it measures is the part the advisory's own PoC section describes, and the patch commit linked below is the authoritative confirmation.

Fix

Cap the header section — both count and byte size — and pass packet_size / line_length to :erlang.decode_packet/3 instead of the empty option list, so the parser itself refuses an oversized line. Apply the same bound to the trailer accumulator.

Related

CVE-2026-59249 is a separate defect in the same HTTP/1 parser — chunk-size desync via Integer.parse/2 sign tolerance.