Bokeh server applications have Incomplete Origin Validation in WebSockets
Description
Bokeh is an interactive visualization library written in Python. In versions 3.8.1 and below, if a server is configured with an allowlist (e.g., dashboard.corp), an attacker can register a domain like dashboard.corp.attacker.com (or use a subdomain if applicable) and lure a victim to visit it. The malicious site can then initiate a WebSocket connection to the vulnerable Bokeh server. Since the Origin header (e.g., http://dashboard.corp.attacker.com/) matches the allowlist according to the flawed logic, the connection is accepted. Once connected, the attacker can interact with the Bokeh server on behalf of the victim, potentially accessing sensitive data, or modifying visualizations. This issue is fixed in version 3.8.2.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
bokehPyPI | < 3.8.2 | 3.8.2 |
Affected products
1Patches
1cedd113b0e27handle wildcard host with port correctly
3 files changed · +12 −2
src/bokeh/server/util.py+5 −0 modified@@ -217,6 +217,7 @@ def match_host(host: str, pattern: str) -> bool: False ''' + # This is for a wildcard match without any port restriction if pattern == "*": return True @@ -233,6 +234,10 @@ def match_host(host: str, pattern: str) -> bool: if pattern_port is not None and host_port != pattern_port: return False + # This is for a wildcard match including any port restriction + if pattern == "*": + return True + host_parts = host.split('.') pattern_parts = pattern.split('.')
tests/unit/bokeh/server/test_util.py+2 −0 modified@@ -129,6 +129,8 @@ def test_match_host() -> None: assert util.match_host('alice:80', '*') is True assert util.match_host('alice:80', '*:80') is True assert util.match_host('alice:8080', '*:80') is False + assert util.match_host("192.168.0.1:80", '*:80') is True + assert util.match_host("192.168.0.1:8080", '*:80') is False #----------------------------------------------------------------------------- # Dev API
tests/unit/bokeh/server/views/test_ws.py+5 −2 modified@@ -76,6 +76,7 @@ def test_uses_auth_request_handler() -> None: pytest.param(["*"], "http://example.com", id="global wildcard"), pytest.param(["example.*"], "http://example.com", id="partial wildcard"), pytest.param(["example.com:*"], "http://example.com", id="port wildcard"), + pytest.param(["*:81"], "http://example.com:81", id="host wildcard"), pytest.param(["example.com:80"], "http://example.com", id="implicit port 80"), pytest.param(["example.com:8080"], "http://example.com:8080", id="explicit port"), pytest.param(["example.com"], "http://example.com", id="exact match"), @@ -97,9 +98,11 @@ async def test_ws_handler_accepts_allowed_origins( BAD_ORIGIN_CASES = ( pytest.param(["example.com"], "http://example.com.bad.com", id="subdomain rejection"), - pytest.param(["example.com"], "http://foo.com", id="exact name mismatch"), + pytest.param(["example.com"], "http://foo.com", id="exact host mismatch"), + pytest.param(["example.com:80"], "http://foo.com:80", id="exact host mismatch with port"), pytest.param(["example.com.*"], "http://example.com", id="pattern mismatch"), - pytest.param(["example.com:80"], "http://example.com:8080", id="port mismatch"), + pytest.param(["example.com:80"], "http://example.com:8080", id="port mismatch with exact host"), + pytest.param(["*:81"], "http://example.com:8080", id="port mismatch with wildcard host"), )
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-793v-589g-574vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-21883ghsaADVISORY
- aydinnyunus.github.io/2026/01/24/bokeh-websocket-hijacking-cve-2026-21883ghsaWEB
- github.com/bokeh/bokeh/commit/cedd113b0e271b439dce768671685cf5f861812eghsax_refsource_MISCWEB
- github.com/bokeh/bokeh/security/advisories/GHSA-793v-589g-574vghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.