CVE-2026-110314 (GCVE-0-2026-110314)
Vulnerability from cvelistv5
Published
2026-10-26 13:31
Modified
2026-10-26 13:31
CWE
  • CWE-602 - Client-Side Enforcement of Server-Side Security
Summary
Client-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. When 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. This issue affects bandit: from 0.5.1 before 1.14.0.
Impacted products
Vendor Product Version
mtrudel bandit Version: 0.5.1   ≤
    cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
Create a notification for this product.
   mtrudel bandit Version: bb407f2277dcb65a58dd8cc07913a0b58a80e266
    cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
Create a notification for this product.
Show details on NVD website


{
  "containers": {
    "cna": {
      "affected": [
        {
          "collectionURL": "https://repo.hex.pm",
          "cpes": [
            "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
          ],
          "defaultStatus": "unaffected",
          "packageName": "bandit",
          "packageURL": "pkg:hex/bandit",
          "product": "bandit",
          "repo": "https://github.com/mtrudel/bandit",
          "vendor": "mtrudel",
          "versions": [
            {
              "lessThan": "1.14.0",
              "status": "affected",
              "version": "0.5.1",
              "versionType": "semver"
            }
          ]
        },
        {
          "collectionURL": "https://github.com",
          "cpes": [
            "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*"
          ],
          "defaultStatus": "unaffected",
          "packageName": "mtrudel/bandit",
          "packageURL": "pkg:github/mtrudel/bandit",
          "product": "bandit",
          "repo": "https://github.com/mtrudel/bandit",
          "vendor": "mtrudel",
          "versions": [
            {
              "lessThan": "220b9dfd27f9a21305be2afc75cbe585ddc24e7a",
              "status": "affected",
              "version": "bb407f2277dcb65a58dd8cc07913a0b58a80e266",
              "versionType": "git"
            }
          ]
        }
      ],
      "cpeApplicability": [
        {
          "nodes": [
            {
              "cpeMatch": [
                {
                  "criteria": "cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*",
                  "versionEndExcluding": "1.14.0",
                  "versionStartIncluding": "0.5.1",
                  "vulnerable": true
                }
              ],
              "negate": false,
              "operator": "OR"
            }
          ],
          "operator": "AND"
        }
      ],
      "credits": [
        {
          "lang": "en",
          "type": "reporter",
          "value": "Peter Ullrich"
        },
        {
          "lang": "en",
          "type": "remediation developer",
          "value": "Mat Trudel"
        },
        {
          "lang": "en",
          "type": "analyst",
          "value": "Jonatan M\u00e4nnchen / EEF"
        }
      ],
      "dateAssigned": "2026-10-23T12:47:59.000Z",
      "descriptions": [
        {
          "lang": "en",
          "supportingMedia": [
            {
              "base64": false,
              "type": "text/html",
              "value": "\u003cp\u003eClient-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\u0027s Close frame.\u003c/p\u003e\n\u003cp\u003eWhen a WebSock callback returns \u003ccode\u003e{:stop, ...}\u003c/code\u003e, \u003ccode\u003eBandit.WebSocket.Connection\u003c/code\u003e calls \u003ccode\u003eterminate/2\u003c/code\u003e, sends Close and shuts down the write side, but keeps the connection process running in a \u003ccode\u003e:closing\u003c/code\u003e state. \u003ccode\u003ehandle_frame/3\u003c/code\u003e and \u003ccode\u003ehandle_info/3\u003c/code\u003e never check that state, so later frames still reach \u003ccode\u003ehandle_in/2\u003c/code\u003e and later messages reach \u003ccode\u003ehandle_info/2\u003c/code\u003e, with the state from the stop tuple, until the client sends Close, TCP drops or the idle timeout fires. A client refused in \u003ccode\u003einit/1\u003c/code\u003e after a failed token check could still run an admin-only action.\u003c/p\u003e\n\u003cp\u003eThis issue affects bandit: from 0.5.1 before 1.14.0.\u003c/p\u003e"
            },
            {
              "base64": false,
              "type": "text/markdown",
              "value": "Client-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\u0027s 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."
            }
          ],
          "value": "Client-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\u0027s 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."
        }
      ],
      "impacts": [
        {
          "capecId": "CAPEC-202",
          "descriptions": [
            {
              "lang": "en",
              "supportingMedia": [
                {
                  "base64": false,
                  "type": "text/html",
                  "value": "\u003cp\u003eA remote client that ignores the Close frame can keep invoking a WebSock application\u0027s handlers after the application refused or ended its session, bypassing an authorization check in \u003ccode\u003einit/1\u003c/code\u003e or a kick or ban. This affects WebSock applications that rely on \u003ccode\u003e{:stop, ...}\u003c/code\u003e to end a session.\u003c/p\u003e"
                },
                {
                  "base64": false,
                  "type": "text/markdown",
                  "value": "A remote client that ignores the Close frame can keep invoking a WebSock application\u0027s 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."
                }
              ],
              "value": "A remote client that ignores the Close frame can keep invoking a WebSock application\u0027s 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."
            }
          ]
        }
      ],
      "metrics": [
        {
          "cvssV4_0": {
            "Automatable": "NOT_DEFINED",
            "Recovery": "NOT_DEFINED",
            "Safety": "NOT_DEFINED",
            "attackComplexity": "LOW",
            "attackRequirements": "PRESENT",
            "attackVector": "NETWORK",
            "baseScore": 6.3,
            "baseSeverity": "MEDIUM",
            "privilegesRequired": "NONE",
            "providerUrgency": "NOT_DEFINED",
            "subAvailabilityImpact": "NONE",
            "subConfidentialityImpact": "NONE",
            "subIntegrityImpact": "NONE",
            "userInteraction": "NONE",
            "valueDensity": "NOT_DEFINED",
            "vectorString": "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",
            "version": "4.0",
            "vulnAvailabilityImpact": "NONE",
            "vulnConfidentialityImpact": "LOW",
            "vulnIntegrityImpact": "LOW",
            "vulnerabilityResponseEffort": "NOT_DEFINED"
          },
          "format": "CVSS",
          "scenarios": [
            {
              "lang": "en",
              "value": "GENERAL"
            }
          ]
        }
      ],
      "problemTypes": [
        {
          "descriptions": [
            {
              "cweId": "CWE-602",
              "description": "CWE-602 Client-Side Enforcement of Server-Side Security",
              "lang": "en",
              "type": "CWE"
            }
          ]
        }
      ],
      "providerMetadata": {
        "dateUpdated": "2026-10-26T13:31:18.257Z",
        "orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
        "shortName": "EEF"
      },
      "references": [
        {
          "name": "WebSocket frames keep reaching the WebSock after a server-initiated close in Bandit",
          "tags": [
            "vendor-advisory",
            "related"
          ],
          "url": "https://github.com/mtrudel/bandit/security/advisories/GHSA-2v5v-8f9v-xh85"
        },
        {
          "name": "EEF CNA record for CVE-2026-110314",
          "tags": [
            "related"
          ],
          "url": "https://cna.erlef.org/cves/CVE-2026-110314.html"
        },
        {
          "name": "OSV record EEF-CVE-2026-110314",
          "tags": [
            "related"
          ],
          "url": "https://osv.dev/vulnerability/EEF-CVE-2026-110314"
        },
        {
          "name": "Introducing commit bb407f2 in mtrudel/bandit",
          "tags": [
            "related"
          ],
          "url": "https://github.com/mtrudel/bandit/commit/bb407f2277dcb65a58dd8cc07913a0b58a80e266"
        },
        {
          "name": "Fix commit 220b9df in mtrudel/bandit",
          "tags": [
            "patch"
          ],
          "url": "https://github.com/mtrudel/bandit/commit/220b9dfd27f9a21305be2afc75cbe585ddc24e7a"
        }
      ],
      "source": {
        "discovery": "UNKNOWN"
      },
      "title": "WebSocket frames keep reaching the WebSock after a server-initiated close in Bandit",
      "workarounds": [
        {
          "lang": "en",
          "supportingMedia": [
            {
              "base64": false,
              "type": "text/html",
              "value": "\u003cp\u003eMark the WebSock state as stopped whenever a callback returns \u003ccode\u003e{:stop, ...}\u003c/code\u003e, and have \u003ccode\u003ehandle_in/2\u003c/code\u003e and \u003ccode\u003ehandle_info/2\u003c/code\u003e return \u003ccode\u003e{:ok, state}\u003c/code\u003e without acting while that mark is set.\u003c/p\u003e"
            },
            {
              "base64": false,
              "type": "text/markdown",
              "value": "Mark 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."
            }
          ],
          "value": "Mark 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."
        }
      ],
      "x_proofOfConcept": [
        {
          "lang": "en",
          "supportingMedia": [
            {
              "base64": false,
              "type": "text/html",
              "value": "\u003col\u003e\n\u003cli\u003eServe a WebSock whose \u003ccode\u003einit/1\u003c/code\u003e returns \u003ccode\u003e{:stop, :normal, {4401, \"unauthorized\"}, state}\u003c/code\u003e for a wrong token, and whose \u003ccode\u003ehandle_in/2\u003c/code\u003e runs an admin-only \u003ccode\u003edelete_all_users\u003c/code\u003e action when \u003ccode\u003euser_id\u003c/code\u003e is \u003ccode\u003eadmin\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003eUpgrade with \u003ccode\u003e?user_id=admin\u0026amp;token=wrong\u003c/code\u003e. The server calls \u003ccode\u003eterminate/2\u003c/code\u003e and sends a Close frame with code 4401.\u003c/li\u003e\n\u003cli\u003eIgnore the Close frame and send a masked text frame \u003ccode\u003edelete_all_users\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehandle_in/2\u003c/code\u003e runs \u003ccode\u003edelete_all_users\u003c/code\u003e as \u003ccode\u003eadmin\u003c/code\u003e.\u003c/li\u003e\n\u003c/ol\u003e"
            },
            {
              "base64": false,
              "type": "text/markdown",
              "value": "1. 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\u0026token=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`."
            }
          ],
          "value": "* 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.\n* Upgrade with ?user_id=admin\u0026token=wrong. The server calls terminate/2 and sends a Close frame with code 4401.\n* Ignore the Close frame and send a masked text frame delete_all_users.\n* handle_in/2 runs delete_all_users as admin."
        }
      ],
      "x_technicalAnalysis": [
        {
          "lang": "en",
          "supportingMedia": [
            {
              "base64": false,
              "type": "text/html",
              "value": "\u003cp\u003e\u003cstrong\u003e1. Stop without close.\u003c/strong\u003e In \u003ccode\u003eBandit.WebSocket.Connection\u003c/code\u003e, \u003ccode\u003edo_stop/4\u003c/code\u003e calls \u003ccode\u003eterminate/2\u003c/code\u003e, sends the Close frame and runs \u003ccode\u003eshutdown(:write)\u003c/code\u003e. It then returns \u003ccode\u003e{:continue, %{connection | state: :closing}}\u003c/code\u003e, so \u003ccode\u003eBandit.WebSocket.Handler\u003c/code\u003e keeps the process alive.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. No state check.\u003c/strong\u003e \u003ccode\u003ehandle_frame/3\u003c/code\u003e and \u003ccode\u003ehandle_info/3\u003c/code\u003e never check \u003ccode\u003econnection.state\u003c/code\u003e. Every later data frame still reaches \u003ccode\u003ehandle_in/2\u003c/code\u003e, and every later message still reaches \u003ccode\u003ehandle_info/2\u003c/code\u003e, with the state from the stop tuple.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Client-controlled lifetime.\u003c/strong\u003e 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 \u003ccode\u003edo_stop\u003c/code\u003e are affected: \u003ccode\u003e{:stop, :normal, ...}\u003c/code\u003e, \u003ccode\u003e{:stop, {:shutdown, :disconnected}, ...}\u003c/code\u003e and \u003ccode\u003e{:stop, {:shutdown, :restart}, ...}\u003c/code\u003e, which is what Phoenix\u0027s \u003ccode\u003e:socket_drain\u003c/code\u003e returns. A banned user who was kicked kept posting for longer than the idle timeout.\u003c/p\u003e"
            },
            {
              "base64": false,
              "type": "text/markdown",
              "value": "**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\u0027s `:socket_drain` returns. A banned user who was kicked kept posting for longer than the idle timeout."
            }
          ],
          "value": "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\n2. 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\n3. 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\u0027s :socket_drain returns. A banned user who was kicked kept posting for longer than the idle timeout."
        }
      ]
    }
  },
  "cveMetadata": {
    "assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
    "assignerShortName": "EEF",
    "cveId": "CVE-2026-110314",
    "datePublished": "2026-10-26T13:31:18.257Z",
    "dateReserved": "2026-10-23T12:45:01.107Z",
    "dateUpdated": "2026-10-26T13:31:18.257Z",
    "state": "PUBLISHED"
  },
  "dataType": "CVE_RECORD",
  "dataVersion": "5.2"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
  • Confirmed: The vulnerability is confirmed from an analyst perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
  • Patched: This vulnerability was successfully patched by the user reporting the sighting.
  • Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
  • Not confirmed: The user expresses doubt about the veracity of the vulnerability.
  • Not patched: This vulnerability was not successfully patched by the user reporting the sighting.


Loading…

Loading…