Twisted: Unauthenticated Remote Denial of Service in the Twisted HTTP/2 Server via Rapid Stream ResetGHSA-vpp8-g58w-m8mp
HIGHFix: twisted/twisted#12749GHSA-vpp8-g58w-m8mp is a high-severity (CVSS 7.5) remote code execution vulnerability in twisted. A fix is available for twisted — see the affected versions and patch details below.
Real-World Exposure
twistedReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
Summary
An uncontrolled-resource-consumption vulnerability (CWE-400) in the Twisted HTTP/2 server lets any unauthenticated remote client exhaust server CPU over a single connection. A client that sends a request header block with END_STREAM and then immediately resets the stream makes the server route and render the request before it processes the reset. Reset streams are freed and never count against the concurrent-stream limit, and no reset-rate limiter exists, so the pattern can be repeated at line rate on one connection. This is the HTTP/2 Rapid Reset class, CVE-2023-44487.
Details
For one received segment the h2 events are processed in order in H2Connection.dataReceived. A HEADERS(END_STREAM) followed by RST_STREAM yields RequestReceived, StreamEnded, then StreamReset, so the request is dispatched and rendered on stream end before the reset is handled:
# src/twisted/web/_http2.py (H2Connection.dataReceived)
for event in events:
if isinstance(event, h2.events.RequestReceived):
self._requestReceived(event) # allocate the request
elif isinstance(event, h2.events.StreamEnded):
self._requestEnded(event) # -> requestComplete -> render() synchronously
elif isinstance(event, h2.events.StreamReset):
self._requestAborted(event) # processed last, frees the stream
_requestAborted frees the reset stream, so it never occupies the 100-stream concurrency budget, and nothing in _http2.py counts or throttles opened-and-reset streams. The 2019 flood fix (GHSA-32gv) bounds outbound control-frame buffering only and does not apply to this path.
Affected product: Twisted, twisted.web HTTP/2 server
Affected versions: confirmed against 26.4.0
PoC
On any Twisted HTTP/2 endpoint, open one connection and, in a tight loop, send a HEADERS frame with END_STREAM on a fresh stream identifier immediately followed by a RST_STREAM for that stream, at line rate. No credentials are required. The server renders each request on stream end before the reset frees the stream, and a freed stream never counts toward the concurrent-stream limit, so the loop is never throttled.
Because every cancelled stream is still dispatched in full and reset streams do not accumulate, the connection is never throttled or closed, so the forced work grows with the number of pairs sent. Sustained on one connection this saturates a CPU core under only a trickle of traffic, whereas a client that instead leaves its streams open is closed once it reaches the concurrent-stream limit.
Impact
This is an unauthenticated remote denial of service (CWE-400, CWE-770) by CPU exhaustion. One connection forces unbounded request rendering at a few frames per cancelled request, bypassing the concurrent-stream limit. It is the HTTP/2 Rapid Reset class (CVE-2023-44487), not covered by the earlier Twisted flood fix, with no confidentiality or integrity impact and no code execution.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | twisted | all versions | 26.10.0rc1pip install --upgrade 'twisted==26.10.0rc1' |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for twisted, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update twisted to 26.10.0rc1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-vpp8-g58w-m8mp is resolved across your whole dependency graph.
Workarounds
Cap what an attacker can consume: apply request size, rate and timeout limits in front of the affected component, and run it with memory and CPU limits so exhaustion degrades one worker rather than the whole service.
Frequently Asked Questions
Is GHSA-vpp8-g58w-m8mp in your dependencies?
Find it across PyPI, including transitive dependencies.