Sep 16, 2026·7 min read·13 visits
Ember HTTP/2 in http4s fails to limit outbound response queues, permitting remote attackers to crash the JVM via OutOfMemoryError by flooding control frames while stalling socket reads.
An uncontrolled resource consumption vulnerability (CVE-2026-69213) in the http4s Ember HTTP/2 server and client implementations allows unauthenticated remote attackers to trigger an OutOfMemoryError (OOM) and cause a Denial of Service (DoS) by exploiting unbounded outbound queues.
The HTTP/2 protocol implementation within the http4s Ember server and client modules (http4s-ember-core) suffers from a high-severity resource consumption flaw. The vulnerability resides in the outbound transmission subsystem, which serializes outgoing HTTP/2 frames. An unauthenticated attacker can remotely trigger memory exhaustion by abusing the protocol's automatic control frame exchange mechanisms, resulting in a complete denial of service.
In standard HTTP/2 configurations, servers and clients must process various connection-level control frames such as PING, SETTINGS, and WINDOW_UPDATE. These frames are treated with high priority to maintain connection health, handle flow control windows, and perform diagnostics. Because these operational frames operate below the application stream layer, they are processed and acknowledged automatically without standard stream-level backpressure limits.
The attack surface exists on any network-exposed endpoint utilizing the Ember HTTP/2 backend. Because the vulnerability lies within the library's protocol management layer, no specific application-level route or endpoint configuration is required to exploit it. The flaw exposes both public-facing servers (H2Server) and outbound client connections (H2Client) to remote disruption.
The technical root cause of CVE-2026-69213 lies in the selection of an unbounded queue container for buffering outbound HTTP/2 frames. Specifically, both H2Client and H2Server instantiate their outgoing frame buffers using cats.effect.std.Queue.unbounded. This queue is designed to store outbound frames before they are serialized and written to the network interface by a dedicated, asynchronous consumer fiber known as the writeLoop.
When a remote peer sends an HTTP/2 control frame, the server immediately constructs a corresponding response frame—such as a PING ACK or a SETTINGS acknowledgment—and appends it to this queue. This architecture functions correctly under normal network conditions where the consumer fiber can continuously drain the queue and write the data to the TCP socket. However, because the queue has no upper bound, there is no limit on the number of elements it can hold simultaneously.
If the remote peer maliciously pauses its socket reads (such as by setting its TCP window size to zero or simply refusing to read incoming TCP packets), the server's TCP send buffer quickly fills up. Once the socket buffer is saturated, the writeLoop fiber blocks on its next write operation. Despite the writer being blocked, the server continues to accept incoming control frames from the attacker over the established read path, processing them and pushing response frames into the outbound queue indefinitely. Because the queue lacks backpressure controls, it grows linearly with the attacker's inputs until the JVM heap space is entirely exhausted.
To understand the structural bug, we must analyze the instantiation of the outbound queue in the vulnerable vs. patched codebases of H2Server and H2Client.
In the vulnerable implementation, the queue is created using Queue.unbounded. The following code snippet shows the exact vulnerable line:
// Vulnerable queue creation in H2Server.scala and H2Client.scala
queue <- cats.effect.std.Queue.unbounded[F, Chunk[H2Frame]]Because this queue lacks a capacity limit, the producer fibers generating responses (e.g., PING responses) are never suspended. They continue to call queue.offer successfully, bypassing any potential memory-saving suspension mechanisms. Under load, this leads to rapid memory inflation.
To remediate this issue, the maintainers modified the queue initialization to use a bounded variant, establishing a threshold of 128 chunks. The patched code is shown below:
// Patched queue creation introducing a bound of 128 elements
queue <- cats.effect.std.Queue.bounded[F, Chunk[H2Frame]](128)By switching to Queue.bounded(128), the cats-effect library's cooperative multitasking model is leveraged. When the queue contains 128 chunks of frames and the writeLoop fiber is blocked by a stalled TCP socket, any subsequent call to queue.offer will semantically block (suspend) the calling producer fiber. This introduces natural backpressure, stopping the generation of new outbound frames until the socket is cleared and the queue is drained.
Exploitation of CVE-2026-69213 does not require authentication or specific environment configurations beyond an active HTTP/2 connection. The attack relies on establishing a stable TCP connection to the target server and executing a 'stalled reader' attack sequence.
First, the attacker establishes an HTTP/2 connection and performs the standard handshake. Next, the attacker configures their local socket settings to stop reading bytes from the target. This can be achieved by decreasing the TCP receive window size to zero or using socket options to stall the socket read buffer entirely. At this point, the target server's outbound queue begins accumulating frames because its writeLoop is unable to push data through the network socket.
Once the TCP stream is choked, the attacker continuously floods the target with high-velocity, lightweight HTTP/2 control frames such as PING frames. The target server processes each frame, produces an acknowledgment, and attempts to enqueue it. Because the writeLoop fiber is blocked, the queue size swells exponentially, generating a linear accumulation of Chunk[H2Frame] objects in heap memory. The attack is sustained until the JVM environment crashes due to a heap-exhaustion java.lang.OutOfMemoryError.
The primary impact of this vulnerability is a complete and sustained denial of service (DoS) of the affected JVM process. Because an OutOfMemoryError usually destabilizes the entire Java Virtual Machine, the application will crash, terminating all active connections and rendering the service unavailable to legitimate users. Recovery typically requires a manual or automated restart of the application container.
This vulnerability has been assigned a CVSS v3.1 base score of 7.5 (High), with the vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. The attack requires low complexity, can be initiated over the network by any unauthenticated entity, and demands no user interaction, resulting in a maximum rating for the Availability impact metric.
In client-side configurations where H2Client is utilized, the application is also vulnerable if it connects to an untrusted or compromised HTTP/2 upstream server. A malicious server can apply the identical socket-stalling technique and flood the client with control frames, causing the client-side Scala application to run out of memory and crash. This highlights the importance of patching both servers and outbound client integration libraries.
The primary remediation strategy is upgrading the http4s-ember-core dependency to a patched release. For users on the 0.23.x branch, the vulnerability is fully addressed in version 0.23.35. For applications adopting the 1.0.0 milestone releases, the fix is integrated into version 1.0.0-M47 and subsequent builds.
If immediate software upgrade is not feasible, several defensive workarounds can be implemented to mitigate the threat vector. First, if HTTP/2 is not strictly required by the application architecture, it can be disabled within the Ember server builder by omitting the withHttp2 parameter, reverting communications to standard HTTP/1.1 where this unbounded queue architecture is absent.
Additionally, deploying a robust reverse proxy or Load Balancer (such as Nginx, Envoy, or AWS Application Load Balancer) in front of the http4s application provides an effective mitigation layer. The reverse proxy will handle HTTP/2 protocol termination, manage the connection-level flow control windows, and enforce write timeouts, severing connections that attempt socket stalling before they can exhaust application-tier resources.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
http4s-ember-core http4s | < 0.23.35 | 0.23.35 |
http4s-ember-core http4s | >= 1.0.0-M1, < 1.0.0-M47 | 1.0.0-M47 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-400, CWE-770 |
| Attack Vector | Network |
| CVSS v3.1 | 7.5 (High) |
| Impact | Denial of Service (DoS) |
| Exploit Status | No Public Exploit Available |
| KEV Status | Not Listed |
The software does not properly control the allocation and maintenance of a limited resource, enabling an actor to influence the amount of resources consumed and exhaust them.
Authlib versions 0.15 through 1.9.0 contain an authorization bypass vulnerability (CWE-639) in the RFC 7592 ClientConfigurationEndpoint implementation and its corresponding reference documentation. The framework fails to verify that the presented registration access token belongs to the target client specified in the request, and the official reference implementations for authenticate_client and check_permission omit token-to-client binding checks. Consequently, an authenticated client holding any valid registration access token can read, modify, or delete the registration metadata of arbitrary clients on the authorization server.
CVE-2026-118773 (GHSA-3g3m-2fjr-6pw4) is an uncontrolled resource consumption vulnerability in the node-glob npm package affecting versions 9.0.0 through 14.0.1. Due to missing return statements in the AbortSignal check routines within the directory traversal walker, aborted operations reject the caller promise as expected but continue executing background filesystem reads and pattern matching. Applications that rely on AbortSignal or AbortSignal.timeout() to enforce execution budgets on user-influenced glob operations remain susceptible to CPU, event loop, and disk I/O exhaustion.
CVE-2026-118764 (also tracked as GHSA-g3hv-75wg-m249) is an asymmetric resource consumption vulnerability (CWE-405) in the npm package minimatch prior to version 11.0.1. When pattern compilation is invoked with an optimizationLevel of 2 or higher, the internal preprocessing functions firstPhasePreProcess() and secondPhasePreProcess() in src/index.ts perform unbounded pattern splitting and quadratic pairwise deduplication. An attacker supplying a crafted glob string containing repeated **/.. segments can induce a combinatorial explosion of pattern permutations followed by an O(M^2) comparison routine, blocking the Node.js event loop and exhausting process memory.
A regular expression denial of service (ReDoS) vulnerability exists in @hapi/content from version 3.0.6 prior to 6.0.3. The Content.disposition() function utilizes an unanchored parameter-name regular expression inside a global replacement scan. When processing a Content-Disposition header parameter containing a long token lacking an equals sign, the JavaScript regular expression engine undergoes quadratic O(N^2) backtracking. Because @hapi/content is invoked by @hapi/pez during multipart/form-data request parsing, unauthenticated remote attackers can send a crafted multipart request to any Hapi route with multipart payload parsing enabled, starving the single-threaded Node.js event loop.
A Regular Expression Denial of Service (ReDoS) vulnerability exists in the joi schema description and data validation library for JavaScript. When validating multiple email addresses using the Joi.string().email({ multiple: true }) configuration, the library compiles a separator regular expression with an unanchored greedy leading whitespace quantifier. Supplying an input string containing a long sequence of whitespace characters without the expected delimiter triggers quadratic time complexity during String.prototype.split execution, blocking the single-threaded Node.js event loop.
CVE-2026-119205 (GHSA-mfr5-77xh-9j7h) is a Denial of Service (DoS) vulnerability in pypdf prior to version 6.21.4. The flaw occurs during the cloning or duplication of nested PDF dictionary structures. Because the cycle-detection algorithm within DictionaryObject._clone() tracked generated destination object references rather than the original source indirect references, cyclic reference graphs in source PDFs evaded detection, resulting in an infinite loop and CPU exhaustion.