During recent research into HTTP/2, I found a DoS vulnerability I call MadeYouReset (CVE‑2025‑8671), publicly disclosed on 13 Aug 2025. It lets an attacker create effectively unbounded concurrent work on servers by bypassing HTTP/2’s built-in concurrency limit - causing a denial of service condition. It builds on the flaw behind 2023’s “Rapid Reset”, with a neat twist that slips past the common mitigation. In this post, we’ll cover how MadeYouReset works at a high level.
This research was conducted jointly with Prof. Anat Bremler‑Barr and Yaniv Harel (Tel Aviv University), with partial support from Imperva.
This vulnerability was publicly disclosed on 13 Aug 2025. Concerned or affected vendors are welcome to get in touch by email.
A Quick Preface About Denial Of Service
Almost any service can be taken down with enough resources, so not all denial of service (DoS) attempts indicate a real vulnerability. The key factor is attacker cost: a true vulnerability lets an attacker cause failure with minimal resources.
DoS attacks rely on creating an imbalance where the attacker spends significantly less than the target to cause disruption. Web apps naturally have this asymmetry - requests are cheap to send but expensive to process. While this is great for performance (the web feels fast), it’s also important it won’t be abused.
Even so, asymmetry by itself doesn’t make HTTP DoS‑prone. What matters is the ability to drive that cost at scale, especially via concurrency. If a client can issue only a small number of requests in a time window, impact is limited. For that reason, HTTP/2 includes a built-in mechanism in the protocol intended to cap the amount of concurrent work a single client can impose on a server.
MadeYouReset bypasses that mechanism.
A Quick Intro To HTTP/2
HTTP/2 uses frames as its smallest unit of communication. Each frame carries a specific type of data (e.g., HTTP headers, request/response payload, or protocol control information). Frames are transmitted over a stream - a virtual channel on which HTTP requests and responses are sent. Each stream carries exactly one request–response cycle. There are also control frames - SETTINGS
, WINDOW_UPDATE
, PRIORITY
, RST_STREAM
, GO_AWAY
, PING
- which manage connection and stream behavior (for example connection settings, flow control and prioritization of requests) and are sent on specific streams or at the connection level.
The protocol allows for streams to be opened simultaneously - which means a client could send requests simultaneously. This mechanism greatly improves the efficiency of the conversation, compared to HTTP/1.1 - and is one of the reasons HTTP/2 was adopted so eagerly.

However, concurrency poses a risk - the fact that the protocol allows clients to open many concurrent requests very easily (which is a very good thing) means a malicious client could overload the server.
For that reason HTTP/2 also introduced a parameter called MAX_CONCURRENT_STREAMS
, which is a limit on the number of active streams a client can have.
If a server is overwhelmed, it can reduce this parameter by notifying the client.
The default value for this parameter is 100 in almost all HTTP/2 servers and implementations.
In theory, it’s a good mechanism, but in practice… well…
The Rapid Reset Vulnerability
Before we get into the details of MadeYouReset, we should talk about its predecessor - Rapid Reset.
In October 2023, “Rapid Reset” was made public - a zero-day in the HTTP/2 protocol exploited in the wild. The vulnerability was immediately reported as “the largest DDoS attack to date” by both Cloudflare and Google. The vulnerability impacted almost all HTTP/2 servers and implementations.
Rapid Reset took advantage of one of the new features HTTP/2 introduced - request cancellation - and, unsurprisingly, used it rapidly…
Request Cancellation
HTTP/2 introduced the request cancellation feature - the client can tell the server it is no longer interested in the response to a request it sent. For example, a browser will cancel a request if the user clicks “stop” or navigates to a different page. There is no need to send the response if the client doesn’t need it anymore.
Request cancellation is done using a specific frame called RST_STREAM
, which tells the server the client cancels a stream.
According to the RFC’s stream state machine: whenever a stream is canceled (using an RST_STREAM
frame), it is immediately considered closed.
Why does it matter? Because closed streams are not counted toward the MAX_CONCURRENT_STREAMS
limit.

In theory, canceling a stream should instruct the server to stop working on the HTTP request and abort sending a response - even if it was already calculated - to avoid wasting server resources and bandwidth.
However, in practice, on many servers and implementations, request cancellation only aborts sending the response to the client once its computed.
What happens is that after a request cancellation is received in the HTTP/2 component of the server, the server continues working on the HTTP request and compute the response, forewards the response to the HTTP/2 component - which discard it since the stream was canceled and is closed now.
This behavior is not due to developer laziness - there’s a solid reason for this, which we’ll start to explore in the last section in the post, and expand on in the Technical Details post.
Therefore, request cancellation causes a mismatch between the number of active requests from the HTTP/2 point of view, and the number of actual active request which are being processed (and for which responses are being computed for) in the server’s backend.
The Rapid Reset Attack

As the name suggests, Rapid Reset simply opens streams and immediately cancels them using RST_STREAM
. By opening and then quickly canceling a stream, the HTTP request is processed and a response is computed, but it no longer counts toward MAX_CONCURRENT_STREAMS
.
In other words, an attacker can use stream cancellations to cause servers to process and compute responses to an
unbounded amount of concurrent requests while completely bypassing the MAX_CONCURRENT_STREAMS
limit imposed by
the HTTP/2 protocol.
Compared to a regular HTTP/2 request‑flood attacker - which is capped by MAX_CONCURRENT_STREAMS
- Rapid Reset is far superior - it is limited only by its own throughput.
How Rapid Reset Was Mitigated
The mitigation to Rapid Reset that was used by almost all affected implementations was very straightforward - limit the number of streams a client can cancel. In other words, limit the number of RST_STREAM
frames that a client can send (for example, a limit of 100 is more than enough).
No request cancellation -> no RST_STREAM
-> no Rapid Reset.
And it worked very well, completely mitigating the attack while not affecting legitimate users.
Until…
MadeYouReset
You can probably guess the twist from the name: instead of the client canceling a request, can we make the server cancel it for us?
At first glance, that sounds odd - the server doesn’t “lose interest” in responses - the client does.
We need to broaden the goal: we’re not limited to cancellation - we just need any path that yields RST_STREAM
. Fortunately for us, in HTTP/2, RST_STREAM
is used both for client‑initiated cancellation and also to signal stream errors.
In principle, client‑triggered errors could help - but they’re tricky. Consider HTTP/1.1. it has a class of errors dedicated to client-originated errors - 4xx errors (400, 401, 403, 404, etc.). Those errors mean the request is invalid or immediately rejected (for example, 403 Forbidden), so no backend work occurs. The same is true in HTTP/2 for malformed requests - you can provoke an RST_STREAM
easily, but the server won’t keep working afterwards.
For MadeYouReset to work, the stream must begin with a valid request that the server begins working on, then trigger a stream error so the server emits RST_STREAM
while the backend continues computing the response. That’s why malformed requests are a dead end for our purposes.

In HTTP/1.1, there are only requests - no streams - so there is no other way to create an error, other than malformed requests. In HTTP/2, there’s more surface area: beyond HTTP messages, there are control frames that shape connection flow. By crafting certain invalid control frames or violating protocol sequencing at just the right moment, we can make the server send RST_STREAM
for a stream that already carried a valid request.
In my research, I found six such “make the server send RST_STREAM
” primitives defined by the RFC, so they apply to any RFC‑compliant implementation. I cover them in the Technical Details post.
By using MadeYouReset (any of the six primitives), an attacker doesn’t need to send a single RST_STREAM
, which completely bypasses the common Rapid Reset mitigation (counting client‑initiated cancellations) while achieving the same impact Rapid Reset had.

MadeYouReset Impact
Most affected servers can be driven into a full denial of service by this attack. Some also crash due to out‑of‑memory (OOM).
The effective impact depends on:
- The server’s capacity (CPU, memory, I/O, worker pools).
- The attacker’s send rate (bandwidth and client CPU).
- The resource chosen as the target of the attack (how much processing time it takes
to compute them).
A very strong server could withstand an attack from a weak attacker.
However, from my tests, due to the asymmetric nature of sending a request versus computing a response - and the fact that the attacker can easily create an unbounded number of active requests - most servers are susceptible to a complete DoS, with a significant number also susceptible to an out‑of‑memory (OOM) crash.
Even when some requests don’t reach heavy backend work (e.g., a response is computed early), the high rate of creating and tearing down stream objects - parsing frames, advancing the state machine, maintaining HPACK, cleanup - still consumes significant CPU and memory in the HTTP/2 front end. At scale, that overhead alone can cause serious degradation or DoS.
More details on the attack’s impact and how to measure it appears in the third post in the series: Impact.
Affected Projects
A partial list of affected implementations (to be expanded):
Summary
We’ve covered what MadeYouReset is, the mechanism it abuses, its predecessor (Rapid Reset), the twist that bypasses the common mitigation used for it, and its potential impact. Next, we’ll dig into each piece: the MadeYouReset primitives, how the protocol counts active streams, why backend work often persists after stream resets (which helps explain the broad impact), and how it should be mitigated - all in the Technical Details post.
Acknowledgments
-
Thanks to Christopher Cullen at CERT/CC (VINCE) for coordinating disclosure across more than 100 vendors.
-
Special thanks to the Netty team (Norman Maurer, Jonas Konrad, Julien Viet, and Bryce Anderson) and to Sean Monstar (Rust/h2 and Hyper) for insightful discussions.
-
Thanks to the vendors and organizations who participated in the coordination process for their helpful feedback.