{"id":"GHSA-vpp8-g58w-m8mp","kind":"vulnerability","aliases":["GHSA-vpp8-g58w-m8mp"],"related":[],"title":"Twisted: Unauthenticated Remote Denial of Service in the Twisted HTTP/2 Server via Rapid Stream Reset","description":"## Summary\n\nAn 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.\n\n## Details\n\nFor 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:\n\n```python\n# src/twisted/web/_http2.py  (H2Connection.dataReceived)\nfor event in events:\n    if isinstance(event, h2.events.RequestReceived):\n        self._requestReceived(event)     # allocate the request\n    elif isinstance(event, h2.events.StreamEnded):\n        self._requestEnded(event)        # -> requestComplete -> render() synchronously\n    elif isinstance(event, h2.events.StreamReset):\n        self._requestAborted(event)      # processed last, frees the stream\n```\n\n`_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.\n\nAffected product: Twisted, `twisted.web` HTTP/2 server\nAffected versions: confirmed against 26.4.0\n\n## PoC\n\nOn 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.\n\nBecause 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.\n\n## Impact\n\nThis 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.","summary":"Twisted's HTTP/2 server renders each request whose stream is reset right after the headers, so one connection can force unbounded request processing. An unauthenticated remote client can exhaust server CPU, causing denial of service with no data access or code execution.","severity":"high","cvss":7.5,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"Twisted","fixed":["26.10.0rc1"],"affected":"<= 26.4.0","ecosystem":"pypi"}],"ecosystems":["pypi"],"tags":["cwe-400","cwe-770","http2","package","python","self-hosted","twisted"],"not_affected_if":["http2-terminated-at-proxy","no-http2-server"],"how_to_check":"- Check the installed Twisted version with `pip show Twisted` or `python -c \"import twisted; print(twisted.__version__)\"`, and compare it to the affected range `<= 26.4.0`.\n- Look for HTTP/2 enabled on a Twisted `twisted.web` server, since the flaw is in the `_http2.py` path.","fix":"- Twisted: move to `26.10.0rc1` or later (fixed version per the sources).","status":"open","published_at":"2026-10-29T16:41:06+00:00","first_seen_at":"2026-10-29T16:44:11+00:00","triaged_at":"2026-10-29T16:46:01+00:00","updated_at":"2026-10-29T16:46:01+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-29T16:46:01Z","model":"claude-haiku-5-5","output":{"fix":"- Twisted: move to `26.10.0rc1` or later (fixed version per the sources).","reach":"internet","title":"Twisted: unauthenticated HTTP/2 Rapid Reset lets one connection exhaust CPU","impact":"medium","summary":"Twisted's HTTP/2 server renders each request whose stream is reset right after the headers, so one connection can force unbounded request processing. An unauthenticated remote client can exhaust server CPU, causing denial of service with no data access or code execution.","audience":"developers","category":"package","nicknames":["Rapid Reset"],"who_cares":["python","twisted","self-hosted","http2"],"confidence":0.8,"fix_status":"available","needs_auth":"no","worst_case":"Server CPU exhausted by a single connection, causing denial of service","analyst_note":"","how_to_check":"- Check the installed Twisted version with `pip show Twisted` or `python -c \"import twisted; print(twisted.__version__)\"`, and compare it to the affected range `<= 26.4.0`.\n- Look for HTTP/2 enabled on a Twisted `twisted.web` server, since the flaw is in the `_http2.py` path.","plain_action":"","needs_analyst":false,"not_affected_if":["no-http2-server","http2-terminated-at-proxy"],"who_is_affected":"Applications that run a Twisted `twisted.web` server with HTTP/2 enabled and accept connections from untrusted clients are exposed. Services behind a proxy that terminates HTTP/2, or those that do not serve HTTP/2, are not exposed to this path. The sources do not say whether the flaw is reachable on default configurations.","user_interaction":"no","needs_special_setup":"unknown","severity_if_unknown":"high"},"cost_usd":0.000831,"input_tokens":5236,"output_tokens":614,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-2023':178C,500C '-400':30B,90C,467C '-44487':179C,501C '-770':32B,469C '100':268C '2019':285C '26.4.0':315C '32gv':290C 'access':78B 'accumul':404C 'affect':305C,311C 'alloc':235C 'appli':301C 'block':115C 'bound':291C 'budget':271C 'buffer':296C 'bypass':486C 'cancel':392C,484C 'caus':71B 'class':176C,498C 'client':66B,100C,109C,440C 'close':411C,448C 'code':80B,518C 'concurr':148C,270C,381C,454C,489C 'concurrent-stream':147C,380C,453C,488C 'confidenti':512C 'confirm':313C 'connect':57B,107C,169C,324C,406C,426C,474C 'consumpt':87C 'control':294C 'control-fram':293C 'core':431C 'count':144C,276C,377C 'cover':503C 'cpu':70B,103C,430C,471C 'credenti':355C 'cve':177C,499C 'cwe':29B,31B,89C,466C,468C 'data':77B 'denial':4A,72B,463C 'detail':180C 'dispatch':211C,396C 'earlier':506C 'elif':238C,247C 'end':117C,196C,216C,335C,365C 'endpoint':321C 'event':187C,226C,228C,231C,234C,240C,243C,249C,252C 'everi':391C 'execut':81B,519C 'exhaust':68B,101C,472C 'exist':157C 'fix':287C,509C 'flood':286C,508C 'follow':198C,343C 'forc':59B,414C,475C 'frame':295C,333C,482C 'free':255C,259C,369C 'freed':141C,374C 'fresh':339C 'full':398C 'g58w':19A,24A 'ghsa':17A,22A,289C 'ghsa-32gv':288C 'ghsa-vpp8-g58w-m8mp':16A,21A 'grow':416C 'h2':186C 'h2.events.requestreceived':232C 'h2.events.streamended':241C 'h2.events.streamreset':250C 'h2connection.datareceived':193C,224C 'handl':221C 'header':54B,114C,195C,332C 'host':38B 'http/2':10A,42B,94C,173C,309C,320C,495C 'http2':33B 'http2.py':275C 'identifi':341C 'immedi':121C,342C 'impact':457C,515C 'instead':442C 'integr':514C 'isinst':230C,239C,248C 'last':254C 'leav':443C 'let':96C 'limit':150C,156C,383C,456C,491C 'line':165C,352C 'loop':329C,386C 'm8mp':20A,25A 'make':125C 'never':143C,265C,376C,388C,408C 'noth':273C 'number':419C 'occupi':266C 'one':56B,168C,182C,323C,425C,473C 'open':280C,322C,446C 'opened-and-reset':279C 'order':191C 'outbound':292C 'packag':34B 'pair':421C 'path':304C 'pattern':160C 'per':483C 'poc':316C 'process':62B,135C,189C,253C 'product':306C 'python':35B,222C 'rapid':13A,26A,174C,496C 'rate':155C,166C,353C 'reach':451C 'receiv':183C 'remot':3A,65B,99C,462C 'render':44B,130C,213C,245C,360C,478C 'repeat':163C 'request':46B,61B,113C,132C,209C,237C,362C,477C,485C 'requestabort':258C 'requestcomplet':244C 'requestreceiv':203C 'requir':357C 'reset':15A,27A,50B,122C,137C,138C,154C,175C,219C,261C,282C,368C,400C,497C 'reset-r':153C 'resourc':86C 'right':51B 'rout':128C 'rst':200C,346C 'satur':428C 'segment':184C 'self':37B 'self-host':36B 'self._requestaborted':251C 'self._requestended':242C 'self._requestreceived':233C 'send':111C,330C 'sent':422C 'server':11A,43B,69B,95C,102C,127C,310C,359C 'servic':6A,74B,465C 'singl':106C 'src/twisted/web/_http2.py':223C 'still':395C 'stream':14A,48B,118C,124C,139C,149C,197C,201C,215C,257C,262C,269C,283C,336C,340C,347C,350C,364C,371C,375C,382C,393C,401C,445C,455C,490C 'streamend':204C 'streamreset':206C 'summari':82C 'sustain':423C 'synchron':246C 'throttl':278C,389C,409C 'tight':328C 'toward':378C 'traffic':437C 'trickl':435C 'twist':1A,9A,39B,40B,93C,307C,319C,507C 'twisted':28A 'twisted.web':308C 'unauthent':2A,64B,98C,461C 'unbound':60B,476C 'uncontrol':85C 'uncontrolled-resource-consumpt':84C 'version':312C 'via':12A 'vpp8':18A,23A 'vulner':88C 'wherea':438C 'whose':47B 'work':415C 'yield':202C","nicknames":["Rapid Reset"],"mitigations":[],"who_affected":"Applications that run a Twisted `twisted.web` server with HTTP/2 enabled and accept connections from untrusted clients are exposed. Services behind a proxy that terminates HTTP/2, or those that do not serve HTTP/2, are not exposed to this path. The sources do not say whether the flaw is reachable on default configurations.","tier":"A","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"developers","plain_action":null,"first_public_at":"2026-10-29T16:41:06+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-vpp8-g58w-m8mp","url":"https://github.com/advisories/GHSA-vpp8-g58w-m8mp","severity":"high","published_at":"2026-10-29T16:41:06+00:00","modified_at":"2026-10-29T16:41:06+00:00","first_seen_at":"2026-10-29T16:44:11+00:00","fetched_at":"2026-10-29T16:44:11+00:00","note":null}],"documents":[{"id":17860,"url":"https://api.github.com/repos/twisted/twisted/security-advisories/GHSA-vpp8-g58w-m8mp","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":17861,"url":"https://github.com/twisted/twisted/security/advisories/GHSA-vpp8-g58w-m8mp","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":17862,"url":"https://github.com/twisted/twisted/pull/12749","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":17863,"url":"https://github.com/twisted/twisted/commit/498939422b69d5a75c4b8f81fa7926423dac534d","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":17864,"url":"https://github.com/twisted/twisted/releases/tag/twisted-26.10.0rc1","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}