aio-libs/aiohttp · error · WebSocketError

1009

1009

Error message

Compressed message has too many deflate members

What it means

Raised as WebSocketError code 1009 (MESSAGE_TOO_BIG) when permessage-deflate (RFC 7692) decompression of an assembled compressed message raises TooManyMembersError from the zlib decompressor. aiohttp's ZLibDecompressor caps the number of deflate members it will emit as a decompression-bomb / zip-bomb defense; exceeding that cap is treated as an oversized message even though the raw compressed bytes were small.

Solutions

  1. Keep permessage-deflate disabled for untrusted peers, or negotiate a smaller/no-context-takeover window.
  2. Set a max_msg_size on the WebSocket reader so oversized decompressed messages are bounded.
  3. Rate-limit / authenticate peers before accepting compressed frames.
  4. If the payload is legitimately large, switch to an uncompressed BINARY channel and chunk at the application layer.

Example fix

// before: deflate enabled with no bound, server accepts untrusted clients
app['ws'] = web.WebSocketResponse(compress=15)
// after: disable compression for untrusted peers or cap message size
app['ws'] = web.WebSocketResponse(compress=0, max_msg_size=1*1024*1024)
Defensive patterns

Strategy: validation

Validate before calling

// Cap message size and prefer no compression for untrusted peers.
ws = web.WebSocketResponse(compress=0, max_msg_size=4*1024*1024)
# or, when compression is required, enforce a strict limit:
ws = web.WebSocketResponse(compress=15, max_msg_size=1*1024*1024)

Type guard

null

Try / catch

try:
    msg = await ws.receive()
except WebSocketError as exc:
    if exc.code == WSCloseCode.MESSAGE_TOO_BIG:
        log.security('possible deflate zip-bomb from %s', peer)
    await ws.close(code=exc.code)

Prevention

When it happens

Trigger: self._decompressobj.decompress_sync(assembled_payload + WS_DEFLATE_TRAILING, ...) throws TooManyMembersError. This occurs when a compressed WebSocket message expands into many zlib members — a classic zip-bomb pattern where a tiny compressed payload yields a huge or pathological decompressed stream.

Common situations: A malicious peer sends a crafted deflate zip-bomb; a legitimate but poorly compressed stream with many flush points; interop with a compressor library that emits many small members; enabling permessage-deflate (compress=15) against untrusted clients without size limits.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/322bf17f6f7fb538. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/_websocket/reader_py.py:261

            if compressed:
                if not self._decompressobj:
                    self._decompressobj = ZLibDecompressor(suppress_deflate_header=True)
                # XXX: It's possible that the zlib backend (isal is known to
                # do this, maybe others too?) will return max_length bytes,
                # but internally buffer more data such that the payload is
                # >max_length, so we return one extra byte and if we're able
                # to do that, then the message is too big.
                try:
                    payload_merged = self._decompressobj.decompress_sync(
                        assembled_payload + WS_DEFLATE_TRAILING,
                        (
                            self._max_msg_size + 1
                            if self._max_msg_size
                            else self._max_msg_size
                        ),
                    )
                except TooManyMembersError as exc:
                    raise WebSocketError(
                        WSCloseCode.MESSAGE_TOO_BIG,
                        "Compressed message has too many deflate members",
                    ) from exc
                if self._max_msg_size and len(payload_merged) > self._max_msg_size:
                    raise WebSocketError(
                        WSCloseCode.MESSAGE_TOO_BIG,
                        f"Decompressed message exceeds size limit {self._max_msg_size}",
                    )
            elif type(assembled_payload) is bytes:
                payload_merged = assembled_payload
            else:
                payload_merged = bytes(assembled_payload)

            size = len(payload_merged)
            if opcode == OP_CODE_TEXT:
                if self._decode_text:
                    try:
                        text = payload_merged.decode("utf-8")

View on GitHub (pinned to d041d4d0fd)