CVE-2026-54466: Verified Reproduction
CVE-2026-54466: websocket-driver: message corruption via abuse of draft-WebSocket length headers
CVE-2026-54466 is verified against faye/websocket-driver-node · npm. Affected versions: < 0.7.5. Fixed in 0.7.5. Vulnerability class: RCE. This critical reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00292.
What Is CVE-2026-54466?
CVE-2026-54466 (GHSA-xv26-6w52-cph6) is a critical message-integrity flaw in the npm package websocket-driver (faye/websocket-driver-node) before 0.7.5. Its legacy draft-75/draft-76 parser mishandles an overlong variable-width length header, causing WebSocket messages to be misframed, corrupted, or lost. Pruva reproduced it (reproduction REPRO-2026-00292).
CVE-2026-54466 Severity & CVSS Score
CVE-2026-54466 is rated critical severity, with a CVSS base score of 9.2 out of 10.
Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.
Affected faye/websocket-driver-node Versions
faye/websocket-driver-node · npm versions < 0.7.5 are affected.
How to Reproduce CVE-2026-54466
pruva-verify REPRO-2026-00292 curl -O https://www.pruva.dev/api/v1/reproductions/REPRO-2026-00292/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-54466
- reached the target end-to-end
- full exploit chain demonstrated
- on the real production code path
- high confidence
- the upstream fix blocks the same trigger
raw bytes on an established hixie-75 WebSocket TCP connection: 0x80 0xFF 0xFF 0xFF 0x7F overlong length header followed by a valid sentinel text frame
- TCP loopback
- Driver.server()
- Draft75.parse()
- _parseLeadingByte()
- stage-1 base-128 length accumulator (no _maxLength guard)
- stage-2 skip mode consumes/misframes the following sentinel text frame
reproduction_steps.sh How the agent worked
Root Cause and Exploit Chain for CVE-2026-54466
websocket-driver (npm package websocket-driver, repo faye/websocket-driver-node) versions before 0.7.5 mishandle an overlong variable-width length header in the legacy draft-75 / draft-76 (hixie-75 / hixie-76) WebSocket parser. The stage-1 base-128 length accumulator in lib/websocket/driver/draft75.js (this._length = (octet & 0x7F) + 128 * this._length) has no upper bound, so a small, attacker-controlled wire sequence makes the declared _length exceed _maxLength (0x3ffffff = 67108863). The driver stays open and enters stage-2 "skip" mode with a gigantic _length, which then consumes, loses, or misframes a subsequent valid text frame instead of delivering its intended message boundary. The 0.7.5 fix adds a per-octet guard if (this._length > this._maxLength) return this.close(); immediately after accumulation.
- Package/component affected:
websocket-driver, theDraft75/Draft76parser path (lib/websocket/driver/draft75.js), reached viaDriver.server(...)/Server.parse(chunk). - Affected versions:
websocket-driver < 0.7.5(representative vulnerable build0.7.4; fixed control0.7.5). - Risk level and consequences: Critical protocol-level message corruption / message loss on any server that accepts legacy hixie-75/76 framing. An attacker who can write raw bytes to an established connection can cause the parser to drop the framing boundary of the following legitimate frame and emit a stale/misframed application message, defeating message integrity. This is a denial-of-integrity / message-corruption issue, not RCE and not memory exhaustion; the wire allocation stays tiny.
Impact Parity
- Disclosed/claimed maximum impact: Protocol message corruption / message loss (impact class
other), via abuse of the draft-75/76 length header on a negotiated hixie-75/76 connection. - Reproduced impact from this run: On
0.7.4, the driver remained open (readyState=1) after the oversized length header, did not deliver the uniquely-marked sentinel text frame, and instead re-emitted a stale copy of the previous (positive-control) message — i.e. the sentinel was consumed/misframed. On0.7.5, the identical stream closed (readyState=3,closeevent) while accumulating the over-limit length header and emitted no corrupted application message. Positive control delivered in both builds, proving the parser/listener path was operational. - Parity:
full— the reproduced behavior matches the disclosed message-corruption/message-loss impact and the fixed-build negative control exactly. - Not demonstrated: No code execution, no memory exhaustion, no native crash — none claimed.
Root Cause
In Draft75.prototype.parse the per-octet state machine has three relevant stages:
- stage 0 (
_parseLeadingByte): a leading byte with the high bit set ((octet & 0x80) === 0x80) initializes_length = 0and moves to stage 1 (binary/control frame with a variable-width length header). - stage 1 (length accumulator): for every octet,
_length = (octet & 0x7F) + 128 * this._length. An octet whose high bit is clear is the terminator; if_length !== 0the parser transitions to stage 2 with_skipped = 0. - stage 2 (skip mode): while
_lengthis truthy, each incoming octet increments_skippeduntil_skipped === _length; the only early exit isoctet === 0xFF, which emitsBuffer.from(this._buffer)as a message and resets to stage 0. Crucially, in the high-bit-set leading-byte branch_parseLeadingBytedoes not resetthis._buffer, so the stale buffer from a prior text frame is still referenced.
In versions < 0.7.5 there is no bound check on _length during stage 1. The minimal deterministic sequence
0x80 0xFF 0xFF 0xFF 0x7F
drives the accumulator: 0 → 127 → 16383 → 2097151 → 268435455. The terminating 0x7F (high bit clear) produces _length = 268435455, which exceeds _maxLength (0x3ffffff = 67108863), and the parser falls through to stage-2 skip mode. A following valid sentinel text frame (0x00 <utf8 "SENTINEL-VULN-XYZ-12345"> 0xFF) is then consumed byte-by-byte as "skipped" payload; when the sentinel's 0xFF terminator is reached, stage 2 emits Buffer.from(this._buffer) — the stale previous message (POSITIVE-CTRL-OK) — and resets to stage 0. The sentinel text is never delivered; a corrupted/stale message is delivered in its place. The connection stays open, so the corruption is silent and ongoing.
The 0.7.5 patch (lib/websocket/driver/draft75.js, one added line in case 1) inserts the guard immediately after accumulation:
this._length = (octet & 0x7F) + 128 * this._length;
if (this._length > this._maxLength) return this.close(); // <-- added in 0.7.5
so the over-limit header closes the driver (readyState=3, close event) before any sentinel is accepted and before any corrupted message can be emitted.
Fix reference: websocket-driver@0.7.5 release — https://github.com/faye/websocket-driver-node/releases/tag/0.7.5 ; advisory GHSA-xv26-6w52-cph6.
Reproduction Steps
- Reference:
bundle/repro/reproduction_steps.sh(driver script) andbundle/repro/harness.js(Node TCP peer harness). Both are self-contained; the script reuses the prepared project cache (bundle/project_cache_context.json,prepared=true) when available and otherwise falls back tonpm install websocket-driver@0.7.4/@0.7.5. - What the script does:
- Resolves the vulnerable (
0.7.4) and fixed (0.7.5) publishedwebsocket-driverpackages from the durable project cache (or installs them). - Verifies the versions and the presence/absence of the
_maxLengthguard indraft75.jsfor each build. - For each build, runs
harness.js, which starts a real loopback TCP server usingnet.createServer+Driver.server({requireMasking:false}), pipesconnection ↔ driver.io, and a raw TCP client performs a draft-75 handshake (nosec-websocket-*headers, soServer.httpselectsDraft75), waits for the101response, then sends (a) a positive-control text frame0x00 "POSITIVE-CTRL-OK" 0xFF, then (b) the malicious overlong length header0x80 0xFF 0xFF 0xFF 0x7Fimmediately followed by (c) a uniquely-marked sentinel text frame0x00 "SENTINEL-VULN-XYZ-12345" 0xFF. All emittedopen/message/close/errorevents, thereadyState, and the exact wire bytes are recorded to JSON. - Evaluates the oracle: vulnerable build must deliver the positive control, stay open (no
close), not deliver the sentinel, and emit a corrupted/stale message; fixed build must deliver the positive control, emitcloseon the malicious header, and emit no corrupted message. - Writes
bundle/repro/runtime_manifest.json,bundle/repro/validation_verdict.json, and best-effort copies proof artifacts into the project-cache proof-carry directory.
- Resolves the vulnerable (
- Expected evidence of reproduction (see
bundle/logs/oracle_eval.txt,bundle/logs/vuln_result.json,bundle/logs/fixed_result.json):- Vulnerable 0.7.4:
positive_delivered=true,sentinel_delivered=false,close_after_malicious=false,corrupted_message_after_malicious=true,positive_count=2(stale re-emit),readyState_final=1. - Fixed 0.7.5:
positive_delivered=true,sentinel_delivered=false,close_after_malicious=true,corrupted_message_after_malicious=false,positive_count=1,readyState_final=3. - Oracle
CONFIRMED=trueonly when both paths match.
- Vulnerable 0.7.4:
Evidence
bundle/logs/reproduction_steps.log— full driver script transcript.bundle/logs/vuln_result.json— events + wire bytes for0.7.4.bundle/logs/fixed_result.json— events + wire bytes for0.7.5.bundle/logs/vuln_run.log,bundle/logs/fixed_run.log— raw harness stdout per build.bundle/logs/oracle_eval.txt— oracle evaluation summary.bundle/repro/runtime_manifest.json— runtime manifest (entrypoint_kind=tcp_peer,service_started=true,healthcheck_passed=true,target_path_reached=true).bundle/repro/validation_verdict.json— structured verdict (claim_outcome=confirmed).
Key excerpt (vulnerable 0.7.4 events):
[
{"type":"open"},
{"type":"message","data":"POSITIVE-CTRL-OK"},
{"type":"message","data":"POSITIVE-CTRL-OK"} // stale re-emit; sentinel "SENTINEL-VULN-XYZ-12345" never delivered
]
Key excerpt (fixed 0.7.5 events):
[
{"type":"open"},
{"type":"message","data":"POSITIVE-CTRL-OK"},
{"type":"close"} // guard closes on over-limit length header; no corrupted message
]
Wire bytes (hex): malicious header 80ffffff7f → computed _length = 268435455 > maxLength 67108863; sentinel 0053454e54494e454c2d56554c4e2d58595a2d3132333435ff.
Environment: Node.js v24.x on Linux; net loopback TCP; websocket-driver 0.7.4 (vulnerable) and 0.7.5 (fixed) from the published npm package; no sanitizers (production path, sanitizer_used=false).
Recommendations / Next Steps
- Upgrade: Pin
websocket-driverto>= 0.7.5(the guardif (this._length > this._maxLength) return this.close();is the canonical fix). - Defense in depth: Where legacy hixie-75/76 framing is not required, disable draft-75/76 negotiation at the HTTP upgrade layer so
Server.httpnever selectsDraft75/Draft76. - Testing: Add a regression test that feeds
0x80 0xFF 0xFF 0xFF 0x7F+ a sentinel text frame throughDriver.server()over a real socket and asserts (1) acloseevent fires and (2) nomessageevent carries stale/sentinel content. Also assert the positive-control text frame still delivers. - Variant exploration (next stage): Confirm the same accumulator path is reachable through
Draft76(hixie-76) once the 8-byte challenge body is satisfied, and check whether intermediate_lengthvalues just under_maxLengthcan still cause partial misframing without tripping the guard.
Additional Notes
- Idempotency: The script was run twice consecutively with identical results (oracle
CONFIRMED=trueboth times, identical event shapes and wire bytes); the only nondeterministic field is the event timestampt. - Minimal deterministic octet sequence derived from shipped source:
0x80 0xFF 0xFF 0xFF 0x7F(5 bytes) drives_lengthto268435455, the smallest terminator-using sequence that exceeds_maxLength(67108863) using all-high-bit continuation octets. The physical wire input (header + sentinel) is under 40 bytes — no resource exhaustion. - Real package, real socket: The proof exercises the published
websocket-driverpackage throughDriver.server(...).parse(chunk)over a real loopback TCP socket (not a copied parser or reimplementation), satisfying thenetwork_protocol/tcp_peerentrypoint requirement. - Limitations: The oracle uses an in-process
netloopback peer; no external network is involved. The driver'sclose()in the fixed build ends the connection before the sentinel, sosentinel_delivered=falsein both builds — the discriminator is thecloseevent plus the absence of a corrupted/stale message in the fixed build versus the stale re-emit + open state in the vulnerable build.
CVE-2026-54466 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired. Phases: support · claim contract · reproduction · judge · variant analysis
Full session Replay every step — scrub the timeline or play it back.
cd /data/pruva/project-cache/a075e72d-24e1-4a1d-a5ed-88cbb2e0a266 && ls -la repo vuln fixed 2>/dev/null; echo "---vuln---"; ls -la vuln 2>/dev/null; echo "---fixed---"; ls -la fixed 2>/dev/null{"cwd":"/data/pruva/runs/294686a0-89e7-4ba8-a27d-7fca0e3a9e70","duration_ms":7,"exit_code":0,"stderr":"","stdout":"fixed:\ntotal 8\ndrwxr-xr-x 1 vscode vscode 82 Jul 16 08:11 .\ndrwxr-xr-x 1 vscode vscode 62 Jul 16 08:18 ..\ndrwxr-xr-x 1 vscode vscode 158 Jul 16 08:11 node_modules\n-rw-r--r-- 1 vscode vscode 2085 Jul 16 08:11 package-lock.json\n-rw-r--r-- 1 vscode vscode 299 Jul 16 08:11 package.json\n\nrepo:\ntotal 40\ndrwxr-xr-x 1 vscode vscode 194 Jul 16 08:10 .\ndrwxr-xr-x 1 vscode vscode 62 Jul 16 08:18 ..\ndrwxr-xr-x 1 vscode vscode 122 Jul 16 10:42 .git\ndrwxr-xr-x 1 vscode vscode 18 Jul 16 08:10 .github\n-rw-r--r-- 1 vscode vscode 31 Jul 16 08:10 .gitignore\n-rw-r--r-- 1 vscode vscode 4358 Jul 16 08:10 CHANGELOG.md\n-rw-r--r-- 1 vscode vscode 162 Jul 16 08:10 CODE_OF_CONDUCT.md\n-rw-r--r-- 1 vscode vscode 558 Jul 16 08:10 LICENSE.md\n-rw-r--r-- 1 vscode vscode 12960 Jul 16 08:10 README.md\ndrwxr-xr-x 1 vscode vscode 52 Jul 16 08:10 examples\ndrwxr-xr-x 1 vscode vscode 18 Jul 16 08:10 lib\n-rw-r--r-- 1 vscode vscode 1204 Jul 16 08:10 package.json\ndrwxr-xr-x 1 vscode vscode 36 Jul 16 08:10 spec\n\nvuln:\ntotal 8\ndrwxr-xr-x 1 vscode vscode … [truncated]cd /data/pruva/project-cache/a075e72d-24e1-4a1d-a5ed-88cbb2e0a266 && echo "=== vuln pkg ==="; cat vuln/package.json; echo "=== fixed pkg ==="; cat fixed/package.json; echo "=== vuln node_modules ==="; ls vuln/node_modules; echo "=== fixed node_modules ==="; ls fixed/node_modules=== vuln pkg ===
{
"name": "vuln",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"Artifacts and Evidence for CVE-2026-54466
Scripts, logs, diffs, and output captured during the reproduction.
How to Fix CVE-2026-54466
Upgrade faye/websocket-driver-node · npm to 0.7.5 or later.
FAQ: CVE-2026-54466
Does CVE-2026-54466 lead to remote code execution?
How is CVE-2026-54466 fixed?
Which versions of websocket-driver are affected by CVE-2026-54466?
How can I reproduce CVE-2026-54466?
References for CVE-2026-54466
Authoritative sources for CVE-2026-54466 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.