EEF-CVE-2026-105659 CVE-2026-105659 GHSA-69m6-wcv4-rwcf
HTTP/2 blocked-send deadlines reset on every WINDOW_UPDATE, letting a trickle pin stream processes indefinitely in Bandit
- Severity
- 0.0 / 10
- Unknown · CVSS 4.0
- Exploit chance (EPSS)
- 0.62%
- Chance of exploitation in the next 30 days. Higher than 49.55% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-400 Uncontrolled Resource Consumption
- Fix
1.13.0- Upgrade bandit to this version or later
Description
Summary
Uncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small WINDOW_UPDATE frames. A stream whose response outruns the connection or stream send window blocks until the peer grants more window, and each block carries a 15 second deadline. Both deadlines restart whenever the peer makes partial progress. Bandit.HTTP2.Connection.finish_data/5 re-queues a partially advanced send in pending_sends with a fresh expires_at, so every WINDOW_UPDATE on stream 0, and every SETTINGS frame, resets the clock. Bandit.HTTP2.Stream.send_data/3 recurses on each stream WINDOW_UPDATE and re-arms a fresh after read_timeout. A peer that grants a few bytes every few seconds keeps the blocked stream process, its Plug state and any resource it holds mid-response alive for as long as it keeps dripping, and repeats this across streams and connections to exhaust server processes and memory.
The two paths are independent. The stream-level block is reached on its own by widening the connection window with a large WINDOW_UPDATE on stream 0 and shrinking the per-stream window with a small SETTINGS_INITIAL_WINDOW_SIZE, so closing only the connection-level path leaves it exploitable.
This issue affects bandit: from 1.4.0 before 1.13.0.
Details
1. Connection-level block (lib/bandit/http2/connection.ex)
A send that outruns the connection send window is queued in pending_sends with an expires_at deadline computed in finish_data/5 as now plus @pending_send_timeout (15 seconds). expire_pending_sends/1 drops any queued send whose deadline has passed whenever data arrives on the connection. do_pending_sends/2 runs on every WINDOW_UPDATE on stream 0 and on every SETTINGS frame (including an empty one, through handle_frame/3). When the granted window is smaller than the queued remainder, the leftover is re-queued through finish_data/5, which recomputes expires_at from the current time instead of preserving the original deadline. Each partial advance therefore restarts the timer, and an empty SETTINGS frame restarts it without granting any window at all.
2. Stream-level block (lib/bandit/http2/stream.ex)
send_data/3 runs in the stream process. When the stream send window is exhausted it enters a receive that waits for {:bandit, {:send_window_update, delta}} with after stream.read_timeout (the struct default of 15 seconds, never overridden in init/5). On each stream WINDOW_UPDATE it applies the delta and recurses into send_data/3 with the remainder, which re-enters the same receive with a fresh after timeout. Each partial advance restarts the 15 second wait.
For the stream-level block to be the one stuck, the stream window must be exhausted while the connection window has room. The peer controls both: a large WINDOW_UPDATE on stream 0 opens the connection window, and a small SETTINGS_INITIAL_WINDOW_SIZE shrinks every stream window. Closing only the connection-level path therefore leaves the stream-level path fully exploitable.
3. Effect
The trickle also keeps the transport-level read timeout from firing, since the connection continuously receives valid frames. The blocked stream process, its Plug state, and anything it holds mid-response stay retained for as long as the peer keeps dripping, instead of being released after 15 seconds.
Proof of concept
Connection-level path:
- Open a stream whose response exceeds the connection send window (65,535 bytes by default).
- The server sends the window's worth and queues the remainder in
pending_sends. - Send
WINDOW_UPDATEfor stream 0 with an increment of 1 every few seconds, or an emptySETTINGSframe. Each one re-queues the remainder with a fresh deadline, soexpire_pending_sends/1never expires it.
Stream-level path:
- Send a large
WINDOW_UPDATEfor stream 0 so the connection window never blocks. - Advertise a small
SETTINGS_INITIAL_WINDOW_SIZEso each stream window is tiny. - Request a response larger than the stream window. The server sends the window's worth and blocks in the recursive
receiveinsend_data/3. - Send
WINDOW_UPDATEfor the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a freshread_timeout. - Repeat across streams, up to
max_concurrent_streamsper connection, and across connections.
Impact
A remote, unauthenticated peer requests a response larger than a send window, then drips tiny WINDOW_UPDATE frames every few seconds. Each blocked stream process, with its Plug state and any upstream connection or file handle it holds, stays alive for as long as the trickle continues. Repeated across many streams and connections this exhausts server processes and memory.
Workarounds
Disable HTTP/2 with http_2_options: [enabled: false], which removes both vulnerable paths. Alternatively, terminate HTTP/2 at a reverse proxy that enforces its own send timeouts and speaks HTTP/1 to Bandit.
Affected versions
Hex
| Package | Vulnerable | Fixed in |
|---|---|---|
| bandit | >= 1.4.0, < 1.13.0 |
1.13.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
bandit Hex → 1.13.0
- mix
{:bandit, "~> 1.13.0"} # then: mix deps.update bandit
Severity breakdown
CVSS 4.0 0.0
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
- Attack vector
- Network
- Attack complexity
- Low
- Attack requirements
- None
- Privileges required
- None
- User interaction
- None
- Confidentiality (vulnerable system)
- None
- Integrity (vulnerable system)
- None
- Availability (vulnerable system)
- High
- Confidentiality (subsequent systems)
- None
- Integrity (subsequent systems)
- None
- Availability (subsequent systems)
- None
Weakness
CWE-400: Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource.
How to prevent it
- Architecture and Design: Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
- Architecture and Design: Mitigation of resource exhaustion attacks requires that the target system either: recognizes the attack and denies that user further access for a given amount of time, or uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed. The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question. The second solution is simply difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply makes the attack require more resources on the part of the attacker.
- Architecture and Design: Ensure that protocols have specific limits of scale placed on them.
- Implementation: Ensure that all failures in resource allocation place the system into a safe posture.
References
Advisories
Fixes and patches
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| EEF-CVE-2026-116078 HTTP/2 request bodies made of 1-byte DATA frames amplify memory use in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-108529 SETTINGS and stream-0 WINDOW_UPDATE frames trigger quadratic work over blocked streams in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-110499 Client-chosen HPACK encoder table size is applied uncapped, growing per-connection state with every response in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-109584 HTTP/2 cookie crumb join expands a small HPACK header block into an unbounded allocation in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-111103 HTTP/2 StreamError recovery rolls back connection state and bypasses per-connection limits in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-111101 HTTP/1.1 chunked request bodies made of 1-byte chunks amplify memory use in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-113436 Quadratic bignum parsing of Content-Length and Host port values in Bandit | Not rated yet | Also affects bandit |
| EEF-CVE-2026-110314 WebSocket frames keep reaching the WebSock after a server-initiated close in Bandit | Not rated yet | Also affects bandit |
Credits: Jonatan Männchen / EEF, Mat Trudel, Thomas Cioppettini
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor