This is less a broken front door than a note slot that leaks paper only if your own code stuffs it wrong
ws before 8.20.1 can disclose uninitialized process memory when application code calls websocket.close() with a TypedArray as the reason argument. The published affected upstream range is >= 8.0.0 < 8.20.1; Debian tracks affected node-ws packages in bullseye, bookworm, and trixie, while Ubuntu had not yet published package-level fix status across supported releases at the time of review.
The vendor's MEDIUM 4.4 is still a little generous for enterprise patch triage. The decisive friction is that exploitation is not a normal unauthenticated network path; it depends on *application misuse* or an attacker already having enough influence over application logic to make the process pass a TypedArray into close(), which is why even the upstream GitHub advisory remarks that real-world severity is believed to be low.
3 steps from start to impact.
Reach a ws-backed code path
ws library in a reachable server or client workflow. This is already narrower than a product CVE because ws is a dependency, not a directly fingerprintable exposed service with a stable banner. Weaponized tooling here is usually just a bespoke PoC using Node.js and ws itself, as shown in the GitHub advisory.- A target application includes upstream
wsin the vulnerable range>=8.0.0 <8.20.1 - The attacker can reach the relevant WebSocket workflow over the network or influence a connected peer
- Internet scanners do not reliably fingerprint npm dependency versions
- Many deployments use
wstransitively and never expose a path where close-frame reasons are attacker-influenced
Force application misuse of websocket.close()
websocket.close(code, reason) with a TypedArray rather than a string or Buffer. Per the fix commit, the bug exists because the function used Buffer.allocUnsafe() and failed to overwrite that buffer correctly for this argument type, which can leak dirty memory to the remote peer. In practice this usually means a buggy wrapper, custom middleware, unsafe plugin, or some already-compromised code path that forwards attacker-controlled objects into the close reason.- Application logic passes a
TypedArrayintoclose() - The attacker can influence that argument or the code path invoking it
- This is not the normal API usage pattern for close reasons
- Requiring attacker control over an internal object type is effectively post-bug or post-compromise in most real applications
- Type validation, framework wrappers, or serialization layers often collapse values to strings before this point
Receive leaked heap bytes in the close frame
- A vulnerable
close()invocation occurs - The attacker controls or can observe the remote WebSocket peer receiving the close frame
- Close reason payload size is small, so exfiltration is constrained
- Useful secrets must happen to be resident in adjacent process memory at the right moment
- Even successful leakage may produce low-value garbage rather than credentials or tokens
The supporting signals.
| In-the-wild status | No public evidence of active exploitation found during this review, and it is not known KEV-listed. |
|---|---|
| Proof of concept | Yes. GitHub advisory GHSA-58qx-3vcg-4xpx includes a working Node.js PoC using WebSocketServer and Float32Array(20). |
| EPSS | 0.00012 from the user-supplied intel — effectively near-floor exploit likelihood. |
| KEV status | Not listed in the CISA KEV catalog during this review; Cyber Trackr's public mirror of KEV had no entry for this CVE and reported catalog currency through 2026-05-22. |
| CVSS vector | Vendor/CNA vector is CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N, which already encodes substantial friction: *high attack complexity* and *high privileges required* for a confidentiality-only outcome. |
| Score disagreement | NVD added a 7.5 HIGH ADP score (AV:N/AC:L/PR:N/...) that does not match the CNA assessment. Based on the advisory and fix notes, the CNA score is closer to reality because exploitation depends on a very specific misuse pattern. |
| Affected versions | Upstream affected range is >= 8.0.0 < 8.20.1. |
| Fixed versions | Upstream fix is 8.20.1. Debian marks node-ws 8.20.1+~cs14.19.1-1 fixed in unstable/sid, while bookworm/trixie/bullseye were still tracked as vulnerable with minor-issue notes at review time. |
| Exposure visibility | Poor internet visibility. This is a library flaw inside Node.js applications, so Shodan/Censys/FOFA-style exposure counts are not meaningful for version-level targeting; package inventory and SBOM data matter more than external attack-surface scans. |
| Disclosure and credit | Published by GitHub on 2026-05-12 in the advisory, with CVE/NVD publication on 2026-05-15. Credit is given to Nikita Skovoroda / ChALkeR. |
noisgate verdict.
The single biggest downgrade driver is that exploitation depends on *the victim application misusing the API* by passing a TypedArray into websocket.close(), not on a broadly reachable pre-auth network bug. That makes this a narrow, post-logic flaw with weak attacker economics even in very large Node.js estates.
Why this verdict
- Downgrade for prerequisite stack: attacker needs a vulnerable
wsbuild *and* a reachable code path *and* a way to make the app callclose()with aTypedArray, which compounds friction hard. - Downgrade for blast radius: impact is confidentiality-only and the leak is bounded by a close-frame path, not a general read primitive or code execution path.
- Downgrade for threat signal: no KEV listing, no public exploitation evidence found, and the supplied EPSS is extremely low.
- Keep it above IGNORE: the bug is real, has a published PoC, and can leak process memory if your application or wrapper actually hits the bad call pattern.
Why not higher?
This is not a clean unauthenticated remote exploit against the exposed service layer. The exploit chain relies on unusual application behavior and often implies the attacker already has control over a higher-level logic path or plugin/custom code that feeds the bad argument type.
Why not lower?
It is still a genuine memory disclosure bug with a vendor patch, not a purely theoretical parser edge case. In environments with custom WebSocket wrappers, multi-tenant app logic, or plugins that reflect structured data into close reasons, the misuse condition can exist accidentally.
What to do — in priority order.
- Inventory
wsversions from SBOMs and lockfiles — Find every direct and transitivewsdependency first; this is the only reliable way to scope exposure because network scanners cannot fingerprint it. For aLOWverdict there is no formal mitigation SLA, so treat this as backlog hygiene and complete the inventory during the next normal dependency review cycle. - Review wrappers around
websocket.close()— Search custom code for any wrapper or helper that forwards non-string objects into the closereasonargument, especially typed arrays or binary views. For aLOWverdict there is no formal mitigation SLA, so roll this into routine secure-code review rather than emergency change control. - Enforce type validation at call sites — If you cannot upgrade immediately, clamp close reasons to strings or
Bufferobjects before they reachws, which removes the published trigger condition. For aLOWverdict there is no formal mitigation SLA, so apply this as part of ordinary hardening work where the package is embedded in longer-lived applications. - Prioritize internet-facing multi-tenant Node services first — If you must choose, start with externally reachable apps where tenants or plugins can influence WebSocket session teardown behavior, because that's where the misuse condition is most plausible. For a
LOWverdict there is no formal mitigation SLA, so sequence this within normal patch backlog handling.
- WAF signatures do not help much because the vulnerable condition is an internal API misuse, not a stable HTTP payload pattern.
- Edge network scanning will not reliably tell you whether you're running a vulnerable
wsversion, because this is a library dependency rather than a bannered product. - MFA or SSO controls are mostly irrelevant unless your application bug already requires authenticated misuse; they do not remove the unsafe
close()behavior itself.
Crowdsourced verification payload.
Run this on the target host, container image, build workspace, or CI runner anywhere your Node.js application files are present. Invoke it with python3 check_ws_cve_2026_45736.py /path/to/app and no elevated privileges are normally required; it walks the directory tree, finds installed node_modules/**/ws/package.json files, and reports VULNERABLE, PATCHED, or UNKNOWN.
#!/usr/bin/env python3
# CVE-2026-45736 verifier for installed ws packages
# Usage: python3 check_ws_cve_2026_45736.py /path/to/app
# Exit codes:
# 0 = PATCHED
# 1 = VULNERABLE
# 2 = UNKNOWN / no determination
import json
import os
import re
import sys
from typing import List, Optional, Tuple
VULN_MIN = (8, 0, 0)
FIXED = (8, 20, 1)
def parse_semver(version: str) -> Optional[Tuple[int, int, int, str]]:
if not version or not isinstance(version, str):
return None
m = re.match(r'^\s*v?(\d+)\.(\d+)\.(\d+)(.*)$', version.strip())
if not m:
return None
return (int(m.group(1)), int(m.group(2)), int(m.group(3)), m.group(4) or '')
def is_vulnerable(version: str) -> Optional[bool]:
parsed = parse_semver(version)
if parsed is None:
return None
core = parsed[:3]
return core >= VULN_MIN and core < FIXED
def find_ws_packages(root: str) -> List[Tuple[str, str]]:
results = []
for dirpath, dirnames, filenames in os.walk(root):
# Skip very large or irrelevant trees when possible
base = os.path.basename(dirpath)
if base in {'.git', '.hg', '.svn', '__pycache__'}:
dirnames[:] = []
continue
if 'package.json' not in filenames:
continue
norm = dirpath.replace('\\', '/')
if '/node_modules/ws' not in norm:
continue
pkg_json = os.path.join(dirpath, 'package.json')
try:
with open(pkg_json, 'r', encoding='utf-8') as f:
data = json.load(f)
if data.get('name') == 'ws':
version = str(data.get('version', '')).strip()
results.append((pkg_json, version))
except Exception:
results.append((pkg_json, ''))
return results
def main() -> int:
if len(sys.argv) != 2:
print('UNKNOWN: usage: python3 check_ws_cve_2026_45736.py /path/to/app')
return 2
root = sys.argv[1]
if not os.path.exists(root):
print(f'UNKNOWN: path does not exist: {root}')
return 2
findings = find_ws_packages(root)
if not findings:
print('UNKNOWN: no installed ws package instances found under node_modules')
return 2
vulnerable = []
patched = []
unknown = []
for path, version in findings:
verdict = is_vulnerable(version)
if verdict is True:
vulnerable.append((path, version))
elif verdict is False:
patched.append((path, version))
else:
unknown.append((path, version))
if vulnerable:
print('VULNERABLE: found ws versions in affected range >=8.0.0 <8.20.1')
for path, version in vulnerable:
print(f' - {version or "unknown-version"} @ {path}')
if patched:
print('Also found patched/non-vulnerable instances:')
for path, version in patched:
print(f' - {version or "unknown-version"} @ {path}')
if unknown:
print('Could not parse these instances:')
for path, version in unknown:
print(f' - {version or "unknown-version"} @ {path}')
return 1
if patched and not unknown:
print('PATCHED: all discovered ws instances are outside the vulnerable range or at/above 8.20.1')
for path, version in patched:
print(f' - {version or "unknown-version"} @ {path}')
return 0
print('UNKNOWN: no vulnerable versions found, but one or more ws instances could not be parsed')
if patched:
print('Parsed patched/non-vulnerable instances:')
for path, version in patched:
print(f' - {version or "unknown-version"} @ {path}')
if unknown:
print('Unparsed instances:')
for path, version in unknown:
print(f' - {version or "unknown-version"} @ {path}')
return 2
if __name__ == '__main__':
sys.exit(main())
If you remember one thing.
websocket.close() or you run highly customized multi-tenant WebSocket services. For a LOW noisgate verdict there is no noisgate mitigation SLA and noisgate remediation SLA is no SLA (treat as backlog hygiene), so go straight to normal dependency management: inventory where ws is present this week, prioritize exposed custom apps, and fold upgrades to 8.20.1+ into the next regular maintenance sprint rather than creating an out-of-band patch event.Sources
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.