Skip to content

[META] Transport payload-size limits: consistent, correct, cross-transport enforcement #1907

Description

@oberstet

Summary

This is a tracking epic for a coordinated hardening of payload/message size
limits
across the Autobahn|Python transports (WebSocket + RawSocket) and their
use in Crossbar.io. It originates from a reported denial-of-service issue
(security advisory GHSA-hxp9-w8x3-p566, "permessage-deflate bypasses
maxMessagePayloadSize after inflation", reported by Team Atlanta), but the
investigation surfaced a small cluster of related defects and design gaps that
are best fixed together so the size-limit model is finally coherent.

No development happens on this issue. Each child below is fixed on its own
issue + branch + PR, following our TDD red/green workflow (add the failing test
first, prove it red in CI, then fix, prove it green — all on the child's PR).
This epic is closed manually once all children are done.

The core problem: one knob, four possible meanings

There are two independent axes to a "size limit":

on-the-wire (compressed) at-the-application (uncompressed)
per-frame maxFramePayloadSize (correct) (no knob — covered transitively)
per-message (no knob — covered transitively) maxMessagePayloadSize

Today maxMessagePayloadSize is documented as bounding the "re-assembled
payload" (uncompressed), enforced on send against the uncompressed message,
and enforced by RawSocket against the (uncompressed, no-compression) wire
message — but on the WebSocket receive path (Hybi/v13) it is enforced against
the compressed frame length
, before inflation. A tiny compressed frame can
therefore inflate past the configured limit and reach the application. Hixie
(v0) already accounts uncompressed bytes correctly; Hybi is the outlier.

Agreed design (see child issues for detail)

  • D1 — semantic anchor. maxMessagePayloadSize = maximum size of a single
    serialized WAMP message (transport payload), measured uncompressed
    — i.e.
    before compression on send, after decompression on receive. This is already
    true on send, on RawSocket, and on Hixie receive; we bring Hybi receive in
    line. Default stays 0 (unlimited), so no existing deployment changes.
  • D2 — two knobs, correctly defined. maxMessagePayloadSize (per-message,
    uncompressed) + maxFramePayloadSize (per-frame, wire). The other two matrix
    cells are covered transitively (enforcing the uncompressed per-message cap
    during inflation also bounds a single-frame bomb; bounding uncompressed
    transitively bounds accepted wire bytes). We deliberately do not add more
    knobs — the existing two, defined correctly, are sufficient and less confusing.
  • Enforcement site. Backend-agnostic, at the inflation site in
    onFrameData, so it works identically in message mode and streaming
    (frame-based) mode
    (which differ only by which onMessage* callbacks a
    consumer overrides), and independently of the compression backend
    (deflate / snappy / bzip2 / brotli).

Children (dependency-ordered)

  1. [BUG] permessage-deflate max_message_size cap silently truncates and corrupts messages (missing unconsumed_tail drain) #1908
  2. [BUG] WebSocket maxMessagePayloadSize is enforced against compressed size, bypassed after permessage-deflate inflation #1909
  3. [BUG] Backend-agnostic bounded decompression for permessage-compress (snappy / bzip2 / brotli parity with deflate) #1910
  4. [BUG] asyncio RawSocket receive size limit is hardcoded to 16 MB (no setProtocolOptions), unlike the Twisted backend #1911
  5. [BUG] WebSocket size-limit config: fill the empty compression validator, clarify compressed-vs-uncompressed docs, fix the zip-bomb functest and put it in CI crossbar#2250

Cross-references

  • Security advisory: GHSA-hxp9-w8x3-p566 (public; developed in the open).
  • Related class: CVE-2016-10544 (ws / Node.js — same compressed-vs-decompressed
    boundary mistake).
  • Ties into the Track T testing epic (parametrized both-backend / both-transport
    / multi-codec size-limit coverage; Crossbar zip-bomb functest into CI).

This work is being completed with AI assistance (Claude Code); all commits are
human-reviewed per AI_POLICY.md.

Metadata

Metadata

Assignees

No one assigned

    Type

    Fields

    No fields configured for Task.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions