Sep 16, 2026·7 min read·18 visits
Unbounded HTTP/2 CONTINUATION frames cause JVM heap exhaustion and DoS in http4s Ember.
A critical resource exhaustion vulnerability exists in the http4s Ember HTTP/2 server and client implementations. By failing to limit the size or quantity of incoming HTTP/2 CONTINUATION frames, the engine allows unauthenticated remote attackers to exhaust JVM heap memory, causing a complete Denial of Service.
The vulnerability identified as CVE-2026-69218 is a resource exhaustion flaw within the HTTP/2 parsing implementation of the Ember backend in http4s. Ember is a pure-Scala, streaming HTTP/1 and HTTP/2 implementation built on top of FS2 and Cats Effect. When configured to support HTTP/2, both Ember servers and clients parse low-level binary framing before passing higher-level request objects to the application routing layer.
This specific vulnerability falls under the class of CWE-770 (Allocation of Resources Without Limits or Throttling). It specifically affects the handling of incoming HTTP/2 HEADERS and CONTINUATION frames. The vulnerability exposes a major attack surface: any exposed HTTP/2 port running a vulnerable version of the http4s Ember backend can be targeted.
An unauthenticated remote attacker can exploit this flaw by initiating an endless stream of CONTINUATION frames. Because the application allocates memory dynamically to store these incoming frames before processing them, memory is consumed until the JVM heap space is completely exhausted. This results in an Out Of Memory (OOM) error, triggering a crash and a complete Denial of Service (DoS).
The root cause of CVE-2026-69218 lies in the state transition logic of the HTTP/2 connection loop in H2Connection.scala. Under the HTTP/2 protocol specification (RFC 9113), header blocks that exceed the maximum frame size must be split across an initial HEADERS or PUSH_PROMISE frame and one or more subsequent CONTINUATION frames. When the initial frame is sent without the END_HEADERS flag set, the connection transitions into an incomplete header accumulation state.
In this state, the server expects only CONTINUATION frames for that specific stream, buffering each incoming fragment in memory. The vulnerable implementation in http4s Ember failed to enforce any upper bound on either the total number of consecutive CONTINUATION frames or the cumulative byte size of the buffered fragments. When a new Continuation frame arrived, the engine appended it directly to a list stored in the connection's state (headersInProgress or pushPromiseInProgress).
Additionally, the engine did not apply a timeout on how long a stream could remain in this transitional state, nor did it perform early HPACK-level decoding validation to reject oversized header blocks. An attacker could exploit these omissions by slowly streaming infinite CONTINUATION frames, forcing the server to hold growing memory structures in the JVM heap indefinitely. This represents a classic HTTP/2 Continuation Flood, where resource allocation occurs at the frame processing layer before standard request size limits or application-level middlewares can be invoked.
Prior to the fix, the state machine within H2Connection.scala handled incoming continuation frames using pattern matching without tracking the accumulated fragment size. The following code block illustrates the vulnerable state transition where incoming frames were unconditionally appended to the internal state:
// Vulnerable state matching in H2Connection.scala
case (
c @ H2Frame.Continuation(id, false, _),
H2Connection.State(_, _, _, _, _, _, _, Some((h, cs)), None),
) =>
if (h.identifier == id) {
// Frame is appended unconditionally to the state list
state.update(s => s.copy(headersInProgress = (h, cs ::: c :: Nil).some))
} else { ... }To resolve this, the maintainers modified the connection loop to track cumulative block sizes and validate them against the configured maxHeaderListSize. The state is now managed using ContinuationProgress, which encapsulates the accumulated size of the header fragments. If the combined size of the existing fragments and the incoming frame exceeds the limits, a GOAWAY frame with H2Error.EnhanceYourCalm is issued:
// Patched state matching in H2Connection.scala
case (
c @ H2Frame.Continuation(id, false, _),
H2Connection.State(_, _, _, _, _, _, _, Some(headers), None, _),
) =>
if (headers.first.identifier != id) {
logger.warn("Invalid Continuation - Protocol Error - Issuing GoAway") >>
goAway(H2Error.ProtocolError)
} else if (headers.size + c.headerBlockFragment.size > maxHeaderBlockSize) {
// Protection: Stop buffer accumulation if limit is breached
logger.debug("Header block exceeds maxHeaderListSize - Issuing GoAway") >>
goAway(H2Error.EnhanceYourCalm)
} else {
state.update(s => s.copy(headersInProgress = headers.addContinuation(c).some))
}Furthermore, the HPACK decoder (Hpack.scala) was hardened to track spec-compliant overhead bytes and enforce maximum header limits dynamically during early decoding. The decoder now throws a MessageTooLong exception when the dynamic size limits are exceeded, preventing deep heap allocations:
// Dynamic HPACK decoding limits in Hpack.scala
val listener = new HeaderListener {
def addHeader(name: Array[Byte], value: Array[Byte], sensitive: Boolean): Unit = {
// Explicitly add 32 bytes of overhead per entry as defined in the spec
decodedSize += name.length + value.length + 32
if (decodedSize > maxHeaderListSize) {
throw EmberException.MessageTooLong(maxHeaderListSize.toInt)
}
}
}Exploitation of CVE-2026-69218 does not require authentication or specific system states. The attack operates entirely at the HTTP/2 frame layer, making it highly effective against any exposed service. The attacker begins by opening a standard TCP connection to the target server and performing the HTTP/2 handshakes (preface exchange and SETTINGS negotiation).
Once the connection is established, the attacker sends a HEADERS frame to open a new stream. Crucially, the attacker clears the END_HEADERS flag (0x04 bit) on this frame, signaling that more header block fragments are to follow. The attacker then initiates a rapid stream of CONTINUATION frames on the same stream, keeping the END_HEADERS flag set to false on every subsequent frame.
The server's JVM memory begins to deplete rapidly as these objects are accumulated in memory. Since no data is parsed at the application layer, normal timeout or connection-handling middle-tier logic is bypassed. This attack can be sustained with minimal bandwidth, as the attacker can send tiny frames (or empty fragments) to consume significant heap allocation objects on the server, eventually causing the process to crash due to an OutOfMemoryError.
The concrete security impact of CVE-2026-69218 is a complete and sustained Denial of Service (DoS) of the affected http4s Ember service. Since the vulnerability triggers an OutOfMemoryError within the JVM, the entire application instance crashes. In containerized environments (e.g., Kubernetes), this leads to container termination, and without proper orchestration or recovery policies, the service will remain offline.
The vulnerability is classified with a CVSS v3.1 score of 7.5 (High), reflecting a high availability impact with low complexity and no required privileges. Because the attack takes place at the transport and connection negotiation layer, it cannot be stopped by application-level authentication filters, rate-limiters, or authorization frameworks that operate on parsed HTTP requests.
While the vulnerability does not lead to remote code execution (RCE) or data confidentiality breaches, its simplicity and reliability make it an appealing vector for service disruption. In deployments where http4s Ember clients are configured to connect to untrusted upstream servers, a malicious server could also exploit this vulnerability to crash the client application via downstream HTTP/2 headers.
The primary and recommended remediation is to upgrade http4s dependencies to a patched version. For applications running on the stable 0.23.x branch, upgrade to version 0.23.35 or newer. For applications utilizing the 1.0.x milestone branch, update the dependency configuration to version 1.0.0-M47 or later.
If upgrading the application dependencies is not immediately feasible, network-level mitigations should be implemented. Deploying a robust reverse proxy or Web Application Firewall (WAF) such as Nginx, Envoy, or AWS Application Load Balancer (ALB) in front of the http4s service can filter out anomalous HTTP/2 frame sequences. These proxies handle the HTTP/2 framing layer themselves, enforcing strict limits on header lists and continuation sequences, thereby shielding the vulnerable backend.
Additionally, operations teams should monitor JVM performance metrics, focusing on Garbage Collection (GC) overhead and Heap Memory utilization. Implementing automated monitoring to alert on rapid increases in JVM heap allocation without corresponding request volume spikes can aid in early detection of ongoing continuation flood attacks.
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 Typelevel | < 0.23.35 | 0.23.35 |
http4s-ember-core Typelevel | >= 1.0.0-M1, < 1.0.0-M47 | 1.0.0-M47 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-770 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 | 7.5 (High) |
| Exploit Status | poc |
| KEV Status | Not Listed |
The software allocates memory resources based on incoming HTTP/2 frames without restricting the number or cumulative size of continuous frame sequences, leading to resource exhaustion.
An OS command injection and argument injection vulnerability exists in the Nx build system and its official Maven (@nx/maven) and Gradle (@nx/gradle) plugins. When constructing the project dependency graph, the plugins invoke underlying Java build wrappers via subshells while interpolating unsanitized plugin options directly from nx.json into the command arguments. An attacker who supplies a crafted nx.json file via a repository or pull request can execute arbitrary shell commands with the privileges of the user or CI/CD runner invoking any Nx command that computes the project graph.
AdminNeo versions 4.6.2 through 5.4.1 contain an improper input validation vulnerability (CWE-20) in the version verification endpoint (?script=version). An unauthenticated remote attacker can submit a crafted HTTP POST request containing an array parameter for 'version', which is serialized into a shared server-side temporary cache file without type checking or authentication. On subsequent requests, the application deserializes this cached array and passes it to cookie() and rawurlencode(), triggering an uncaught fatal TypeError in PHP 8.x environments and causing a persistent 24-hour Denial of Service (DoS) for all users.
Charmbracelet Soft Serve versions 0.5.0 through 0.12.2 contain an authorization bypass and observable response discrepancy vulnerability in the HTTP Git server. Remote attackers can determine the existence, exact names, and clone URLs of private repositories by issuing crafted Go remote import path discovery requests (?go-get=1) or by observing differential HTTP status codes across Git Smart HTTP, Dumb HTTP, and Git LFS endpoints.
A Time-of-Check to Time-of-Use (TOCTOU) race condition in Charmbracelet Soft Serve versions 0.6.0 through 0.12.2 allows unauthenticated users to retain read access to repositories after their visibility is changed from public to private. The flaw stems from premature cache invalidation in the SetPrivate method prior to committing the visibility update to the underlying database. Concurrent read requests arriving during the database transaction window repopulate the non-expiring in-memory LRU cache with stale public metadata, bypassing access controls across HTTP, SSH, and Git protocols until the server process is restarted.
A resource consumption vulnerability exists in the Authlib joserfc Python library prior to version 1.7.5. When processing JSON Web Encryption (JWE) objects formatted using General JSON Serialization, the library fails to enforce an upper bound on the number of elements within the recipients array. An unauthenticated attacker can supply a JWE containing thousands of recipient entries targeting an RSA key management algorithm, forcing the server to perform an equivalent number of computationally expensive RSA private-key decryption operations per request and exhausting available CPU resources.
CVE-2026-119284 (GHSA-9pg5-8c7x-5hfx) is an uncontrolled resource consumption vulnerability (CWE-400) in the compact serialization deserialization routines of joserfc prior to version 1.7.6. The library enforced individual segment size limits only after invoking Python's bytes.split(b'.') on untrusted input tokens. By sending oversized compact JWS or JWE tokens, an unauthenticated remote attacker can force the Python interpreter to duplicate and allocate memory for every segment before size validation executes, resulting in memory exhaustion and an Endpoint Denial of Service (DoS).