CVE-2026-39804 Overview
CVE-2026-39804 is an unauthenticated denial-of-service vulnerability in mtrudel/bandit, an HTTP/WebSocket server library for the Erlang BEAM virtual machine. The flaw resides in the WebSocket permessage-deflate decompression path. A single high-ratio compressed frame can force gigabyte-scale heap allocations in the connection process, exhausting node memory and triggering an out-of-memory (OOM) kill. The issue affects Bandit versions from 0.5.9 before 1.11.0 and is classified under CWE-770: Allocation of Resources Without Limits or Throttling.
Critical Impact
An unauthenticated remote attacker who can open a WebSocket connection can crash the entire BEAM node with a single crafted compressed frame, taking down all services hosted on it.
Affected Products
- mtrudel/bandit versions >= 0.5.9 and < 1.11.0
- Applications that explicitly enable Bandit's server-level websocket_options.compress option
- Applications that pass compress: true to WebSockAdapter.upgrade/4 on the per-upgrade path
Discovery Timeline
- 2026-05-01 - CVE-2026-39804 published to NVD
- 2026-05-05 - Last updated in NVD database
Technical Details for CVE-2026-39804
Vulnerability Analysis
The vulnerability lives in Elixir.Bandit.WebSocket.PerMessageDeflate.inflate/2 inside lib/bandit/websocket/permessage_deflate.ex. The function calls :zlib.inflate/2 without any output-size cap and then materializes the entire decompressed payload as a single binary using IO.iodata_to_binary/1. The websocket_options.max_frame_size setting only limits the on-the-wire (compressed) frame size, leaving the decompressed output unbounded.
A uniform-data payload compressed at roughly 1024:1 can stay well below any reasonable wire-size limit while inflating to gigabytes of memory inside the connection process. Because the allocation happens before any application code runs, no business-logic guard can intervene. Stock Phoenix and Phoenix LiveView applications default to compress: false and are not affected.
Root Cause
The root cause is the absence of a maximum inflation ratio or absolute output-size cap in the permessage-deflate decoder. The decoder trusts the compressed input and accumulates the entire inflated stream into a single binary, transforming a small network-bound input into a large memory allocation.
Attack Vector
The attack is fully network-reachable and unauthenticated. An adversary opens a standard WebSocket handshake to a Bandit endpoint that has both websocket_options.compress and the per-upgrade compress: true option enabled. The attacker then sends a single compressed frame containing highly redundant data. The BEAM node allocates GiB-scale memory for the inflated payload and is terminated by the operating system OOM killer, causing service disruption for every tenant on that node.
// Patch excerpt — lib/bandit.ex
+ * `max_inflate_ratio`: The maximum allowable ratio to allow decompression of received WebSocket
+ messages. Intended to prevent 'inflate bomb' attacks where a tiny deflated messages inflates to
+ a massive one. Defaults to `25` representing a 25:1 allowable inflation ratio.
"""
@type websocket_options :: [
{:enabled, boolean()}
| {:max_frame_size, pos_integer()}
| {:validate_text_frames, boolean()}
| {:compress, boolean()}
| {:deflate_options, deflate_options()}
+ | {:max_inflate_ratio, pos_integer()}
]
Source: GitHub commit 8156921
// Patch excerpt — lib/bandit/websocket/connection.ex
{:error, :no_compress} ->
do_error(1002, "Received unexpected compressed frame (RFC6455§5.2)", socket, connection)
+ {:error, :too_much_inflation} ->
+ do_error(1009, "Received compressed frame inflating too much", socket, connection)
+
{:error, _reason} ->
do_error(1007, "Inflation error", socket, connection)
end
Source: GitHub commit 8156921. The fix introduces a max_inflate_ratio (default 25) and aborts decompression with WebSocket close code 1009 (:too_much_inflation) when the inflated size exceeds the ratio.
Detection Methods for CVE-2026-39804
Indicators of Compromise
- BEAM virtual machine processes terminated by the Linux OOM killer with oom_kill_process entries in dmesg or journalctl referencing beam.smp or erlang.
- Sudden memory growth in a single Erlang connection process immediately after a WebSocket upgrade negotiates the permessage-deflate extension.
- Repeated short-lived WebSocket connections from the same source IP that issue a single compressed frame and then disconnect.
Detection Strategies
- Inspect WebSocket handshake responses for Sec-WebSocket-Extensions: permessage-deflate and correlate with subsequent abnormal memory consumption on the server.
- Monitor BEAM telemetry such as :erlang.memory(:processes) and per-process heap size for sharp spikes following inbound WebSocket frames.
- Alert on disproportionate ratios between bytes received on a WebSocket connection and resident memory growth attributable to that connection.
Monitoring Recommendations
- Capture host-level OOM events and BEAM crash dumps and forward them to a centralized logging or SIEM platform for correlation.
- Track Bandit version inventory across deployments to confirm upgrades to 1.11.0 or later.
- Add synthetic checks that send benign compressed WebSocket frames and verify that the server enforces an inflation ratio limit.
How to Mitigate CVE-2026-39804
Immediate Actions Required
- Upgrade mtrudel/bandit to version 1.11.0 or later in all Elixir/Erlang deployments.
- Audit application code for WebSockAdapter.upgrade/4 calls passing compress: true and confirm whether compression is actually required.
- If an upgrade is not immediately possible, disable WebSocket compression by setting websocket_options.compress to false at the Bandit server level.
Patch Information
The fix is delivered in Bandit 1.11.0 via commit 8156921a51e684a951221da7bc30a70a022f722e. It introduces a new max_inflate_ratio option (default 25:1) that bounds decompression output relative to compressed input. Frames exceeding the ratio are rejected with WebSocket close code 1009. Additional details are available in the GitHub Security Advisory GHSA-frh3-6pv6-rc8j and the CNA advisory.
Workarounds
- Set websocket_options.compress to false on the Bandit listener configuration to disable permessage-deflate entirely.
- Remove or set compress: false on WebSockAdapter.upgrade/4 per-route invocations until the library is upgraded.
- Place a reverse proxy that terminates WebSocket compression with its own size and ratio limits in front of Bandit when compression must remain available to clients.
# Bandit configuration example (Elixir) — disable WebSocket compression
# config/runtime.exs
config :my_app, MyAppWeb.Endpoint,
adapter: Bandit.PhoenixAdapter,
http: [
port: 4000,
websocket_options: [
compress: false,
max_frame_size: 1_048_576
]
]
# After upgrading to bandit >= 1.11.0, prefer enforcing a ratio cap:
# websocket_options: [compress: true, max_inflate_ratio: 25]
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


