{"affected":[{"package":{"ecosystem":"Hex","name":"bandit","purl":"pkg:hex/bandit"},"ranges":[{"events":[{"introduced":"0.5.1"},{"fixed":"1.14.0"}],"type":"SEMVER"}],"versions":["0.5.1","0.5.2","0.5.3","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.5.9","0.5.10","0.5.11","0.6.0","0.6.1","0.6.2","0.6.3","0.6.4","0.6.5","0.6.6","0.6.7","0.6.8","0.6.9","0.6.10","0.6.11","0.7.0","0.7.1","0.7.2","0.7.3","0.7.4","0.7.5","0.7.6","0.7.7","1.0.0-pre.1","1.0.0-pre.2","1.0.0-pre.3","1.0.0-pre.4","1.0.0-pre.5","1.0.0-pre.6","1.0.0-pre.7","1.0.0-pre.8","1.0.0-pre.9","1.0.0-pre.10","1.0.0-pre.11","1.0.0-pre.12","1.0.0-pre.13","1.0.0-pre.14","1.0.0-pre.15","1.0.0-pre.16","1.0.0-pre.17","1.0.0-pre.18","1.0.0","1.1.0","1.1.1","1.1.2","1.1.3","1.2.0","1.2.1","1.2.2","1.2.3","1.3.0","1.4.0","1.4.1","1.4.2","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.6.9","1.6.10","1.6.11","1.7.0","1.8.0","1.9.0","1.10.0","1.10.1","1.10.2","1.10.3","1.10.4","1.11.0","1.11.1","1.12.0","1.12.1","1.12.2","1.12.3","1.12.4","1.12.5","1.13.0","1.13.1"]},{"ranges":[{"events":[{"introduced":"bb407f2277dcb65a58dd8cc07913a0b58a80e266"},{"fixed":"220b9dfd27f9a21305be2afc75cbe585ddc24e7a"}],"repo":"https://github.com/mtrudel/bandit","type":"GIT"}]}],"aliases":["GHSA-2v5v-8f9v-xh85","CVE-2026-110314"],"credits":[{"name":"Peter Ullrich","type":"REPORTER"},{"name":"Mat Trudel","type":"REMEDIATION_DEVELOPER"},{"name":"Jonatan Männchen / EEF","type":"ANALYST"}],"database_specific":{"capec_ids":["CAPEC-202"],"cpe_ids":["cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"],"cwe_ids":["CWE-602"]},"details":"## Summary\n\nClient-Side Enforcement of Server-Side Security vulnerability in the WebSocket connection handling of mtrudel bandit allows a remote client to keep running WebSock handler code after the server has stopped its connection, via data frames sent after ignoring the server's Close frame.\n\nWhen a WebSock callback returns `{:stop, ...}`, `Bandit.WebSocket.Connection` calls `terminate/2`, sends Close and shuts down the write side, but keeps the connection process running in a `:closing` state. `handle_frame/3` and `handle_info/3` never check that state, so later frames still reach `handle_in/2` and later messages reach `handle_info/2`, with the state from the stop tuple, until the client sends Close, TCP drops or the idle timeout fires. A client refused in `init/1` after a failed token check could still run an admin-only action.\n\nThis issue affects bandit: from 0.5.1 before 1.14.0.\n\n## Details\n\n**1. Stop without close.** In `Bandit.WebSocket.Connection`, `do_stop/4` calls `terminate/2`, sends the Close frame and runs `shutdown(:write)`. It then returns `{:continue, %{connection | state: :closing}}`, so `Bandit.WebSocket.Handler` keeps the process alive.\n\n**2. No state check.** `handle_frame/3` and `handle_info/3` never check `connection.state`. Every later data frame still reaches `handle_in/2`, and every later message still reaches `handle_info/2`, with the state from the stop tuple.\n\n**3. Client-controlled lifetime.** The connection ends only when the client sends Close, TCP drops, or the idle timeout fires, and each client frame resets that timeout. All stops through `do_stop` are affected: `{:stop, :normal, ...}`, `{:stop, {:shutdown, :disconnected}, ...}` and `{:stop, {:shutdown, :restart}, ...}`, which is what Phoenix's `:socket_drain` returns. A banned user who was kicked kept posting for longer than the idle timeout.\n\n## Proof of concept\n\n1. Serve a WebSock whose `init/1` returns `{:stop, :normal, {4401, \"unauthorized\"}, state}` for a wrong token, and whose `handle_in/2` runs an admin-only `delete_all_users` action when `user_id` is `admin`.\n2. Upgrade with `?user_id=admin&token=wrong`. The server calls `terminate/2` and sends a Close frame with code 4401.\n3. Ignore the Close frame and send a masked text frame `delete_all_users`.\n4. `handle_in/2` runs `delete_all_users` as `admin`.\n\n## Impact\n\nA remote client that ignores the Close frame can keep invoking a WebSock application's handlers after the application refused or ended its session, bypassing an authorization check in `init/1` or a kick or ban. This affects WebSock applications that rely on `{:stop, ...}` to end a session.\n\n## Workarounds\n\nMark the WebSock state as stopped whenever a callback returns `{:stop, ...}`, and have `handle_in/2` and `handle_info/2` return `{:ok, state}` without acting while that mark is set.","id":"EEF-CVE-2026-110314","modified":"2026-10-26T13:31:18.734371Z","published":"2026-10-26T13:31:18.257Z","references":[{"type":"ADVISORY","url":"https://github.com/mtrudel/bandit/security/advisories/GHSA-2v5v-8f9v-xh85"},{"type":"WEB","url":"https://cna.erlef.org/cves/CVE-2026-110314.html"},{"type":"WEB","url":"https://github.com/mtrudel/bandit/commit/bb407f2277dcb65a58dd8cc07913a0b58a80e266"},{"type":"FIX","url":"https://github.com/mtrudel/bandit/commit/220b9dfd27f9a21305be2afc75cbe585ddc24e7a"},{"type":"PACKAGE","url":"https://hex.pm/packages/bandit"}],"related":[],"schema_version":"1.7.3","severity":[{"score":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N","type":"CVSS_V4"}],"summary":"WebSocket frames keep reaching the WebSock after a server-initiated close in Bandit","upstream":[]}