HTTP/3 moves web traffic off TCP and onto QUIC, which runs over UDP. QUIC removes transport-level head-of-line blocking, builds in TLS 1.3, and brings faster connection setup, better delivery on unstable networks, and connection migration. It also brings new state management problems, especially in how application-layer metadata is compressed, sent, and processed.
On October 20, 2026, security research firm FoxIO published an analysis by researcher Sébastien Féry on a class of denial-of-service bugs called “Header Accordions”. A single unauthenticated HTTP/3 connection can make a server allocate thousands of times more memory than the client sent, ending in resource exhaustion, process termination, or sustained CPU lockups. Neither the cryptography nor the QPACK specification is at fault. Major implementations failed to enforce resource limits during the decompression phase.
How HTTP/3, QUIC, and QPACK fit together
HTTP/3 communication has three layers. QUIC runs at the transport layer and provides reliable, encrypted streams over UDP. HTTP/3 sits on top and defines the application-level framing, including HEADERS and DATA frames. QPACK, specified in RFC 9204, compresses the headers.
QPACK exists because HPACK, the HTTP/2 scheme, assumes TCP’s ordering. Under QUIC, a lost packet carrying a dynamic table update would stall header decompression across every stream, which recreates the head-of-line blocking QUIC was built to remove. QPACK avoids this by moving encoder and decoder instructions onto dedicated unidirectional streams, so compression state evolves independently of the requests themselves.
A QPACK decoder keeps a static table of well-known header fields and a dynamic table of entries specific to the connection. A client does not send the full text of headers like User-Agent or Accept-Encoding. It sends compact integer indexes or Huffman-encoded literals, and the server’s decoder rebuilds the field list. This helps performance, and it also changes the trust boundary between the wire format and the server’s memory.
Why QPACK amplification works: compressed size versus heap size
FoxIO’s research exposes one bad assumption: that the size of a compressed payload tracks the memory needed to process it. In Go, Rust, or Python, a header is rarely stored as raw bytes. It ends up in a hash map, a dictionary, or a vector of string objects, and each of those carries overhead. A Go string needs a pointer to the underlying byte array plus a length. A hash map entry needs memory for the key, the value, the hash seed, and the pointers used to navigate buckets.
The network stack sees only the compressed byte count. If the server’s security controls measure only the incoming frame size, an attacker can exploit the gap between the wire format and the heap allocation. A malicious client can build a QPACK block that references one moderately sized dynamic table entry thousands of times. Each reference costs a few bytes on the wire, but the application may instantiate a new string object or map entry for every one.
FoxIO measured amplification of roughly 4,064-to-one against Bun’s HTTP/3 server. Just 5.8 megabytes of transmitted data was enough to trigger resource exhaustion in a two-gigabyte container. At that ratio, a bandwidth-saving optimization works as a denial-of-service vector.
RFC 9114 field section size and the 32-byte overhead rule
The HTTP/3 authors anticipated header expansion. Section 4.2.2 of RFC 9114 requires an implementation to limit the maximum message header size it will accept, and it gives a formula for calculating that size. A field section is measured by the uncompressed length of each field’s name and value, plus a mandatory 32 bytes per field:
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
Subscribe to the Newsletter →The 32-byte constant is a standardized proxy for the structural overhead of typical language data structures. Take a request with ten thousand headers, each with a one-byte name and a one-byte value. The raw text is only twenty thousand bytes, but the formula gives 340,000, because the overhead is applied ten thousand times. The rule protects against death by a thousand cuts, where an attacker sends millions of tiny headers to overwhelm the hash map allocator or the garbage collector.
The SETTINGS_MAX_FIELD_SECTION_SIZE parameter lets endpoints negotiate this limit during the handshake. As the FoxIO research shows, advertising a limit does nothing if the decoder fails to enforce it inside the parsing loop.
Two Header Accordion attack vectors
Féry’s research groups the exploitation into two vectors. Both use valid QPACK instructions to get past naive security checks.
Vector 1: floods of unique literal headers
The first vector sends a huge number of unique, literal headers. The attacker packs hundreds of thousands of distinct, small header fields into one highly compressed block, and Huffman coding keeps them compact on the wire. The server’s decoder must allocate memory for every unique string and insert it into the application’s header map.
FoxIO showed that a single one-megabyte compressed block could force the Go-based quic-go library to retain about fifteen megabytes of live heap after garbage collection. A 15x ratio looks manageable alone, but an attacker can open hundreds of concurrent QUIC connections, each delivering a payload tuned for maximum allocator pressure and garbage collection latency.
Vector 2: dynamic table references
The second vector is considerably worse. The attacker first uses QPACK encoder instructions to insert a large string, perhaps four kilobytes of arbitrary data, into the server’s dynamic table. Then the attacker sends a HEADERS frame made entirely of indexed references to that one entry. The frame is tiny, often a few hundred bytes, because it holds only the integer index.
A vulnerable decoder follows every reference and builds a long list of decoded fields. If the application iterates over those headers or copies them into a request context, the server allocates four kilobytes for each reference. That is how Bun reached its 4,000-fold amplification, and it lets the attacker dictate the server’s memory use with microscopic network payloads. The underlying mistake is conflating table capacity with field section size. The dynamic table limits the state used for compression, but it puts no cap on the cumulative size of the request produced by referencing that state repeatedly.
Affected HTTP/3 implementations and CVEs
Header Accordions are not one bug. They are a spread of implementation errors that end in the same outage.
quic-go (CVE-2025-64702)
quic-go powers infrastructure tools like Caddy, Traefik, and cloudflared. It enforced limits on the size of the incoming compressed HEADERS frame but never measured the cumulative size of the decoded field section. The result was CVE-2025-64702: the decoder allocated unbounded memory as long as the initial packet stayed under the configured threshold. Go’s garbage collector handles short-lived allocations well, but it struggles when hundreds of thousands of live, reachable string pointers sit inside complex map structures.
The fix arrived in version 0.57.0. The maintainers rewrote the decoding loop to track the RFC 9114 field size incrementally and abort the stream with an HTTP 431 error the moment the budget is exceeded. Allocations are stopped in advance instead of cleaned up afterward.
Cloudflare quiche (CVE-2026-12523)
Cloudflare’s quiche library, written in Rust, failed in a more complicated way under CVE-2026-12523. Besides QPACK decompression limits, it had a parser flaw that pre-allocated memory based on a declared frame length before the full payload had arrived. QUIC frames can be fragmented across packets, so an attacker could start a HEADERS frame, declare a massive length in the frame header, and then send the payload very slowly or not at all. The vulnerable parser immediately reserved a contiguous block of virtual memory matching the declared length. That quickly exhausted the system’s memory map and triggered out-of-memory kills at the operating system level.
Version 0.29.3 restructured the parser’s memory management. It replaced eager pre-allocation with incremental, bounds-checked buffer growth that commits physical memory only as bytes are validated and decoded.
Bun and the signed integer overflow
Bun is probably the most alarming case. It uses the lsquic library but replaces the upstream header-handling logic with its own implementation. According to FoxIO, Bun’s custom handler checked the size of individual headers but kept no running total of the decoded field section.
That led to an integer overflow. Once the accumulated decoded header size passed the maximum value of a signed 32-bit integer (about 2.14 gigabytes), the internal counter wrapped to a negative value or zero, depending on the type casting involved. The wrapped counter bypassed the later boundary checks and trapped the server’s event loop in a non-terminating state that held one CPU core at 100 percent. The lesson is that downstream wrappers which bypass upstream parsing logic inherit the full burden of protocol security, and custom implementations rarely get the adversarial testing that foundational libraries do.
Wireshark (CVE-2025-13945)
The threat also reaches analysis tools. Wireshark was vulnerable to a related QPACK decoding flaw, tracked as CVE-2025-13945. Wireshark processes captured PCAP files, often with TLS key-log files to decrypt QUIC payloads after the fact. When an analyst opens a capture containing a crafted QPACK stream, the HTTP/3 dissector tries to rebuild the headers for display. The sheer number of decoded objects in a Header Accordion payload makes the dissector’s internal tree structures balloon until the host runs out of RAM and the application freezes or crashes. A debugging artifact ends up attacking the researcher who is trying to analyze it.
Wireshark fixed this in versions 4.6.2 and 4.4.12 with strict decoding budgets and hard limits on dissector tree depth.
How to build a QPACK decoder that resists header amplification
Validating a payload after it has been fully received and decoded does not work for compression protocols. A secure QPACK decoder needs incremental accounting: it evaluates the cost of every instruction before committing anything to memory. When it meets an indexed field representation, it calculates the logical size of the referenced entry, adds the 32-byte RFC 9114 overhead, and checks the new total against the configured SETTINGS_MAX_FIELD_SECTION_SIZE. If the addition would exceed the limit, the decoder rejects the field, terminates the stream, and releases any partially allocated resources.
Arithmetic in resource counters is a security boundary. Bun’s overflow shows what naive addition costs. A safe decoder checks whether the remaining budget can hold the next field before performing the addition, which rules out wrap-around regardless of the integer width the host language uses.
Limits must also apply uniformly to every QPACK representation type. A common mistake is to count literal header fields carefully while treating dynamic table references as free because the string data already exists in the table. The table entry may consume memory only once, but the application-layer representation of that field in a given request still has structural costs that need accounting.
Finally, robust implementations need backpressure to handle slow-loris style variants of the attack. lsquic addressed this in version 4.9.0 by limiting the number of unpacked header sets a server retains per stream and pausing parsing until the application drains the buffered data. A fast sender then cannot overwhelm a slow consumer, and decoded state does not pile up in the library’s internal queues. Backpressure only works when the producer is barred from creating unbounded buffered work while the consumer falls behind.
Detecting Header Accordion attacks
Detection is hard. The attack uses entirely valid, specification-compliant QPACK instructions, so it produces no malformed packets and no anomalies at the TCP or UDP layer. Web Application Firewalls and Intrusion Detection Systems that rely on signature matching or protocol violation heuristics will usually pass this traffic straight to the backend HTTP/3 server. The malicious intent shows up only in the ratio of network ingress to internal memory allocation.
Monitoring therefore has to move from the network perimeter to the application runtime. Operators should baseline HTTP/3 connection memory footprints and alert on abnormal spikes in heap allocation, garbage collection frequency, or CPU utilization correlated with QUIC listener ports. Where dependency mapping is opaque, as in containerized microservices or managed edge proxies, security teams should inventory the QUIC libraries actually in use. A product can advertise itself as a secure edge gateway and still embed an unpatched version of quic-go or Rust h3.
Mitigation: patch first, disable HTTP/3 if you can’t
Upgrade to the patched versions: quic-go 0.57.0, quiche 0.29.3, and lsquic 4.9.0. Network-level rate limiting is a weak substitute, because an attacker can spread the attack across a botnet of low-bandwidth connections. Rate limits that constrain new connections or excessive resource use from a single source still add some protection, but they do not replace a properly bounded decoder.
Where no patch exists yet, as with Bun or certain Python-based aioquic deployments, the only reliable mitigation is to disable HTTP/3 on public-facing interfaces. For tools like mitmproxy, setting the configuration flag that disables HTTP/3 stops the vulnerable QPACK decoder from ever being instantiated. You lose the latency and performance benefits of QUIC, but you keep the infrastructure available until the vendors catch up with the FoxIO disclosure.
What Header Accordions mean for HTTP/3 security
Header Accordions show that moving from TCP to QUIC does not protect applications from resource exhaustion. It only changes the layer where the attack lands. QPACK’s efficiency gains are real, and they create an asymmetry that attackers will use. RFC 9114 and RFC 9204 give implementers the rules for safe operation, but the rules only help if the code enforces them. Resource limits belong in the decoder’s core execution loop, not in optional configuration or a validation step that runs after the memory is already spent.









