CVE-2026-105659 (GCVE-0-2026-105659)
Vulnerability from cvelistv5 – Published: 2026-10-14 16:54 – Updated: 2026-10-15 18:37
VLAI
EPSS
VEX
Title
HTTP/2 blocked-send deadlines reset on every WINDOW_UPDATE, letting a trickle pin stream processes indefinitely in Bandit
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.
Severity
CWE
- CWE-400 - Uncontrolled Resource Consumption
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/mtrudel/bandit/security/adviso… | relatedvendor-advisory |
| https://cna.erlef.org/cves/CVE-2026-105659.html | related |
| https://osv.dev/vulnerability/EEF-CVE-2026-105659 | related |
| https://github.com/mtrudel/bandit/commit/fff06efe… | related |
| https://github.com/mtrudel/bandit/commit/d88b2195… | patch |
Impacted products
Date Public
2026-10-14 16:54
{
"containers": {
"cna": {
"affected": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Bandit.HTTP2.Connection\u0027",
"\u0027Elixir.Bandit.HTTP2.Stream\u0027"
],
"packageName": "bandit",
"packageURL": "pkg:hex/bandit",
"product": "bandit",
"programFiles": [
"lib/bandit/http2/connection.ex",
"lib/bandit/http2/stream.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
}
],
"repo": "https://github.com/mtrudel/bandit",
"vendor": "mtrudel",
"versions": [
{
"lessThan": "1.13.0",
"status": "affected",
"version": "1.4.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Bandit.HTTP2.Connection\u0027",
"\u0027Elixir.Bandit.HTTP2.Stream\u0027"
],
"packageName": "mtrudel/bandit",
"packageURL": "pkg:github/mtrudel/bandit",
"product": "bandit",
"programFiles": [
"lib/bandit/http2/connection.ex",
"lib/bandit/http2/stream.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
}
],
"repo": "https://github.com/mtrudel/bandit",
"vendor": "mtrudel",
"versions": [
{
"lessThan": "d88b21953c65061f21136ea76a89a4f95a2ac7a1",
"status": "affected",
"version": "fff06efe3be962b484bd4d3eb339372fda5a231f",
"versionType": "git"
}
]
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*",
"versionEndExcluding": "1.13.0",
"versionStartIncluding": "1.4.0",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Thomas Cioppettini"
},
{
"lang": "en",
"type": "reporter",
"value": "Thomas Cioppettini"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Mat Trudel"
}
],
"dateAssigned": "2026-10-12T16:13:25.000Z",
"datePublic": "2026-10-14T16:54:46.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eUncontrolled Resource Consumption vulnerability in mtrudel bandit allows an unauthenticated remote attacker to pin HTTP/2 stream processes indefinitely via a trickle of small \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e 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. \u003ccode\u003eBandit.HTTP2.Connection.finish_data/5\u003c/code\u003e re-queues a partially advanced send in \u003ccode\u003epending_sends\u003c/code\u003e with a fresh \u003ccode\u003eexpires_at\u003c/code\u003e, so every \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0, and every \u003ccode\u003eSETTINGS\u003c/code\u003e frame, resets the clock. \u003ccode\u003eBandit.HTTP2.Stream.send_data/3\u003c/code\u003e recurses on each stream \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e and re-arms a fresh \u003ccode\u003eafter read_timeout\u003c/code\u003e. 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.\u003c/p\u003e\n\u003cp\u003eThe two paths are independent. The stream-level block is reached on its own by widening the connection window with a large \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 and shrinking the per-stream window with a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e, so closing only the connection-level path leaves it exploitable.\u003c/p\u003e\n\u003cp\u003eThis issue affects bandit: from 1.4.0 before 1.13.0.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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.\n\nThe 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.\n\nThis issue affects bandit: from 1.4.0 before 1.13.0."
}
],
"value": "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.\n\nThe 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.\n\nThis issue affects bandit: from 1.4.0 before 1.13.0."
}
],
"impacts": [
{
"capecId": "CAPEC-227",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA remote, unauthenticated peer requests a response larger than a send window, then drips tiny \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"baseScore": 8.7,
"baseSeverity": "HIGH",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "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",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-400",
"description": "CWE-400 Uncontrolled Resource Consumption",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-15T18:37:59.434Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "GitHub Advisory",
"tags": [
"related",
"vendor-advisory"
],
"url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-69m6-wcv4-rwcf"
},
{
"name": "EEF CNA record for CVE-2026-105659",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-105659.html"
},
{
"name": "OSV record EEF-CVE-2026-105659",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-105659"
},
{
"name": "Introducing commit fff06ef in mtrudel/bandit",
"tags": [
"related"
],
"url": "https://github.com/mtrudel/bandit/commit/fff06efe3be962b484bd4d3eb339372fda5a231f"
},
{
"name": "Fix commit d88b219 in mtrudel/bandit",
"tags": [
"patch"
],
"url": "https://github.com/mtrudel/bandit/commit/d88b21953c65061f21136ea76a89a4f95a2ac7a1"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "HTTP/2 blocked-send deadlines reset on every WINDOW_UPDATE, letting a trickle pin stream processes indefinitely in Bandit",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eDisable HTTP/2 with \u003ccode\u003ehttp_2_options: [enabled: false]\u003c/code\u003e, 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "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."
}
],
"value": "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."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eConnection-level path:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eOpen a stream whose response exceeds the connection send window (65,535 bytes by default).\u003c/li\u003e\n\u003cli\u003eThe server sends the window\u0027s worth and queues the remainder in \u003ccode\u003epending_sends\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for stream 0 with an increment of 1 every few seconds, or an empty \u003ccode\u003eSETTINGS\u003c/code\u003e frame. Each one re-queues the remainder with a fresh deadline, so \u003ccode\u003eexpire_pending_sends/1\u003c/code\u003e never expires it.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eStream-level path:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eSend a large \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for stream 0 so the connection window never blocks.\u003c/li\u003e\n\u003cli\u003eAdvertise a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e so each stream window is tiny.\u003c/li\u003e\n\u003cli\u003eRequest a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive \u003ccode\u003ereceive\u003c/code\u003e in \u003ccode\u003esend_data/3\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eSend \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh \u003ccode\u003eread_timeout\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eRepeat across streams, up to \u003ccode\u003emax_concurrent_streams\u003c/code\u003e per connection, and across connections.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Connection-level path:\n\n1. Open a stream whose response exceeds the connection send window (65,535 bytes by default).\n2. The server sends the window\u0027s worth and queues the remainder in `pending_sends`.\n3. Send `WINDOW_UPDATE` for stream 0 with an increment of 1 every few seconds, or an empty `SETTINGS` frame. Each one re-queues the remainder with a fresh deadline, so `expire_pending_sends/1` never expires it.\n\nStream-level path:\n\n1. Send a large `WINDOW_UPDATE` for stream 0 so the connection window never blocks.\n2. Advertise a small `SETTINGS_INITIAL_WINDOW_SIZE` so each stream window is tiny.\n3. Request a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive `receive` in `send_data/3`.\n4. Send `WINDOW_UPDATE` for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh `read_timeout`.\n5. Repeat across streams, up to `max_concurrent_streams` per connection, and across connections."
}
],
"value": "Connection-level path:\n\n* Open a stream whose response exceeds the connection send window (65,535 bytes by default).\n* The server sends the window\u0027s worth and queues the remainder in pending_sends.\n* Send WINDOW_UPDATE for stream 0 with an increment of 1 every few seconds, or an empty SETTINGS frame. Each one re-queues the remainder with a fresh deadline, so expire_pending_sends/1 never expires it.\n\nStream-level path:\n\n* Send a large WINDOW_UPDATE for stream 0 so the connection window never blocks.\n* Advertise a small SETTINGS_INITIAL_WINDOW_SIZE so each stream window is tiny.\n* Request a response larger than the stream window. The server sends the window\u0027s worth and blocks in the recursive receive in send_data/3.\n* Send WINDOW_UPDATE for the stream with an increment of 1 at intervals shorter than 15 seconds. Each one re-arms a fresh read_timeout.\n* Repeat across streams, up to max_concurrent_streams per connection, and across connections."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Connection-level block\u003c/strong\u003e (\u003ccode\u003elib/bandit/http2/connection.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003eA send that outruns the connection send window is queued in \u003ccode\u003epending_sends\u003c/code\u003e with an \u003ccode\u003eexpires_at\u003c/code\u003e deadline computed in \u003ccode\u003efinish_data/5\u003c/code\u003e as now plus \u003ccode\u003e@pending_send_timeout\u003c/code\u003e (15 seconds). \u003ccode\u003eexpire_pending_sends/1\u003c/code\u003e drops any queued send whose deadline has passed whenever data arrives on the connection. \u003ccode\u003edo_pending_sends/2\u003c/code\u003e runs on every \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 and on every \u003ccode\u003eSETTINGS\u003c/code\u003e frame (including an empty one, through \u003ccode\u003ehandle_frame/3\u003c/code\u003e). When the granted window is smaller than the queued remainder, the leftover is re-queued through \u003ccode\u003efinish_data/5\u003c/code\u003e, which recomputes \u003ccode\u003eexpires_at\u003c/code\u003e from the current time instead of preserving the original deadline. Each partial advance therefore restarts the timer, and an empty \u003ccode\u003eSETTINGS\u003c/code\u003e frame restarts it without granting any window at all.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Stream-level block\u003c/strong\u003e (\u003ccode\u003elib/bandit/http2/stream.ex\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esend_data/3\u003c/code\u003e runs in the stream process. When the stream send window is exhausted it enters a \u003ccode\u003ereceive\u003c/code\u003e that waits for \u003ccode\u003e{:bandit, {:send_window_update, delta}}\u003c/code\u003e with \u003ccode\u003eafter stream.read_timeout\u003c/code\u003e (the struct default of 15 seconds, never overridden in \u003ccode\u003einit/5\u003c/code\u003e). On each stream \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e it applies the delta and recurses into \u003ccode\u003esend_data/3\u003c/code\u003e with the remainder, which re-enters the same \u003ccode\u003ereceive\u003c/code\u003e with a fresh \u003ccode\u003eafter\u003c/code\u003e timeout. Each partial advance restarts the 15 second wait.\u003c/p\u003e\n\u003cp\u003eFor 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 \u003ccode\u003eWINDOW_UPDATE\u003c/code\u003e on stream 0 opens the connection window, and a small \u003ccode\u003eSETTINGS_INITIAL_WINDOW_SIZE\u003c/code\u003e shrinks every stream window. Closing only the connection-level path therefore leaves the stream-level path fully exploitable.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Effect\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Connection-level block** (`lib/bandit/http2/connection.ex`)\n\nA 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.\n\n**2. Stream-level block** (`lib/bandit/http2/stream.ex`)\n\n`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.\n\nFor 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.\n\n**3. Effect**\n\nThe 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."
}
],
"value": "1. Connection-level block (lib/bandit/http2/connection.ex)\n\nA 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.\n\n2. Stream-level block (lib/bandit/http2/stream.ex)\n\nsend_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.\n\nFor 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.\n\n3. Effect\n\nThe 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."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-105659",
"datePublished": "2026-10-14T16:54:46.484Z",
"dateReserved": "2026-10-12T10:00:01.326Z",
"dateUpdated": "2026-10-15T18:37:59.434Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"vulnerability-lookup:meta": {
"epss": {
"cve": "CVE-2026-105659",
"date": "2026-10-15",
"epss": "0.00528",
"percentile": "0.43409"
},
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"collectionURL": "https://repo.hex.pm",
"cpes": [
"cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Bandit.HTTP2.Connection\u0027",
"\u0027Elixir.Bandit.HTTP2.Stream\u0027"
],
"packageName": "bandit",
"packageURL": "pkg:hex/bandit",
"product": "bandit",
"programFiles": [
"lib/bandit/http2/connection.ex",
"lib/bandit/http2/stream.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
}
],
"repo": "https://github.com/mtrudel/bandit",
"vendor": "mtrudel",
"versions": [
{
"lessThan": "1.13.0",
"status": "affected",
"version": "1.4.0",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Bandit.HTTP2.Connection\u0027",
"\u0027Elixir.Bandit.HTTP2.Stream\u0027"
],
"packageName": "mtrudel/bandit",
"packageURL": "pkg:github/mtrudel/bandit",
"product": "bandit",
"programFiles": [
"lib/bandit/http2/connection.ex",
"lib/bandit/http2/stream.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:finish_data/5"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:do_pending_sends/2"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Connection\u0027:handle_frame/3"
},
{
"name": "\u0027Elixir.Bandit.HTTP2.Stream\u0027:send_data/3"
}
],
"repo": "https://github.com/mtrudel/bandit",
"vendor": "mtrudel",
"versions": [
{
"lessThan": "d88b21953c65061f21136ea76a89a4f95a2ac7a1",
"status": "affected",
"version": "fff06efe3be962b484bd4d3eb339372fda5a231f",
"versionType": "git"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "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.\n\nThe 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.\n\nThis issue affects bandit: from 1.4.0 before 1.13.0."
}
],
"id": "CVE-2026-105659",
"lastModified": "2026-10-15T19:17:16.013",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "NONE",
"attackVector": "NETWORK",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 8.7,
"baseSeverity": "HIGH",
"confidentialityRequirement": "NOT_DEFINED",
"exploitMaturity": "NOT_DEFINED",
"integrityRequirement": "NOT_DEFINED",
"modifiedAttackComplexity": "NOT_DEFINED",
"modifiedAttackRequirements": "NOT_DEFINED",
"modifiedAttackVector": "NOT_DEFINED",
"modifiedPrivilegesRequired": "NOT_DEFINED",
"modifiedSubAvailabilityImpact": "NOT_DEFINED",
"modifiedSubConfidentialityImpact": "NOT_DEFINED",
"modifiedSubIntegrityImpact": "NOT_DEFINED",
"modifiedUserInteraction": "NOT_DEFINED",
"modifiedVulnAvailabilityImpact": "NOT_DEFINED",
"modifiedVulnConfidentialityImpact": "NOT_DEFINED",
"modifiedVulnIntegrityImpact": "NOT_DEFINED",
"privilegesRequired": "NONE",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "NONE",
"valueDensity": "NOT_DEFINED",
"vectorString": "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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "NONE",
"vulnIntegrityImpact": "NONE",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
},
"published": "2026-10-14T17:18:15.657",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-105659.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/mtrudel/bandit/commit/d88b21953c65061f21136ea76a89a4f95a2ac7a1"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/mtrudel/bandit/commit/fff06efe3be962b484bd4d3eb339372fda5a231f"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-69m6-wcv4-rwcf"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-105659"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Deferred",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-400"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
}
}
}
Loading…
Loading…
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
Loading…
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Loading…
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.