Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-39804

CVE-2026-39804: Bandit WebSocket Compression DoS Vulnerability

CVE-2026-39804 is a denial of service vulnerability in Bandit's WebSocket permessage-deflate compression that enables memory exhaustion attacks. This post covers the technical details, affected versions, impact, and mitigation.

Published:

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.

text
// 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

text
// 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.
bash
# 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.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.