Skip to content

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.

REPRO-2026-00292 faye/websocket-driver-node · npm RCE Known vulnerability Jul 16, 2026 CVE entry .txt
Severity
CRITICAL
CVSS
9.2
Confidence
HIGH
Reproduced in
7m 28s
Tool calls
127
Spend
$1.26
01 · Overview

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).

02 · Severity & CVSS

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 threat level
9.2 / 10 CVSS base
Weakness CWE-130

Critical — the most severe class — typically remotely exploitable with severe impact. Treat as an emergency.

03 · Affected Versions

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
or 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
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for CVE-2026-54466

Security impact — reproduced
  • 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
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

Attack chain
  1. TCP loopback
  2. Driver.server()
  3. Draft75.parse()
  4. _parseLeadingByte()
  5. stage-1 base-128 length accumulator (no _maxLength guard)
  6. stage-2 skip mode consumes/misframes the following sentinel text frame
Runnable proof: reproduction_steps.sh
Captured evidence: fixed run
How the agent worked 302 events · 127 tool calls · 7 min
7 minDuration
127Tool calls
93Reasoning steps
302Events
6Dead-ends
Agent activity over 7 min
Policy
1
Support
16
Repro
83
Judge
76
Variant
121
Verify
1
0:0007:28

Root Cause and Exploit Chain for CVE-2026-54466

Versions: websocket-driver < 0.7.5 (representative vulnerable build 0.7.4; fixed control 0.7.5).

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, the Draft75/Draft76 parser path (lib/websocket/driver/draft75.js), reached via Driver.server(...) / Server.parse(chunk).
  • Affected versions: websocket-driver < 0.7.5 (representative vulnerable build 0.7.4; fixed control 0.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. On 0.7.5, the identical stream closed (readyState=3, close event) 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 = 0 and 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 !== 0 the parser transitions to stage 2 with _skipped = 0.
  • stage 2 (skip mode): while _length is truthy, each incoming octet increments _skipped until _skipped === _length; the only early exit is octet === 0xFF, which emits Buffer.from(this._buffer) as a message and resets to stage 0. Crucially, in the high-bit-set leading-byte branch _parseLeadingByte does not reset this._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

  1. Reference: bundle/repro/reproduction_steps.sh (driver script) and bundle/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 to npm install websocket-driver@0.7.4 / @0.7.5.
  2. What the script does:
    • Resolves the vulnerable (0.7.4) and fixed (0.7.5) published websocket-driver packages from the durable project cache (or installs them).
    • Verifies the versions and the presence/absence of the _maxLength guard in draft75.js for each build.
    • For each build, runs harness.js, which starts a real loopback TCP server using net.createServer + Driver.server({requireMasking:false}), pipes connection ↔ driver.io, and a raw TCP client performs a draft-75 handshake (no sec-websocket-* headers, so Server.http selects Draft75), waits for the 101 response, then sends (a) a positive-control text frame 0x00 "POSITIVE-CTRL-OK" 0xFF, then (b) the malicious overlong length header 0x80 0xFF 0xFF 0xFF 0x7F immediately followed by (c) a uniquely-marked sentinel text frame 0x00 "SENTINEL-VULN-XYZ-12345" 0xFF. All emitted open/message/close/error events, the readyState, 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, emit close on 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.
  3. 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=true only when both paths match.

Evidence

  • bundle/logs/reproduction_steps.log — full driver script transcript.
  • bundle/logs/vuln_result.json — events + wire bytes for 0.7.4.
  • bundle/logs/fixed_result.json — events + wire bytes for 0.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-driver to >= 0.7.5 (the guard if (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.http never selects Draft75/Draft76.
  • Testing: Add a regression test that feeds 0x80 0xFF 0xFF 0xFF 0x7F + a sentinel text frame through Driver.server() over a real socket and asserts (1) a close event fires and (2) no message event 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 _length values just under _maxLength can still cause partial misframing without tripping the guard.

Additional Notes

  • Idempotency: The script was run twice consecutively with identical results (oracle CONFIRMED=true both times, identical event shapes and wire bytes); the only nondeterministic field is the event timestamp t.
  • Minimal deterministic octet sequence derived from shipped source: 0x80 0xFF 0xFF 0xFF 0x7F (5 bytes) drives _length to 268435455, 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-driver package through Driver.server(...).parse(chunk) over a real loopback TCP socket (not a copied parser or reimplementation), satisfying the network_protocol / tcp_peer entrypoint requirement.
  • Limitations: The oracle uses an in-process net loopback peer; no external network is involved. The driver's close() in the fixed build ends the connection before the sentinel, so sentinel_delivered=false in both builds — the discriminator is the close event 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.

Event 1/40
0:000:30
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · CVE-2026-54466 · REPRO-20
0:02
0:03
0:04
web search
0:06
0:07
web search
0:09
0:10
0:11
0:21
0:21
extract_facts
no facts extracted
0:22
0:22
supportclaim_contract
0:24
0:24
0:24
0:25
0:25
0:25
0:26
0:26
0:26
0:26
0:27
0:27
$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]
0:29
0:29
0:29
$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"
08 · How to Fix

How to Fix CVE-2026-54466

Upgrade faye/websocket-driver-node · npm to 0.7.5 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-54466 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-54466

Does CVE-2026-54466 lead to remote code execution?

No. The impact is protocol message corruption and message loss (Integrity High) — not remote code execution or memory exhaustion. It is rated CVSS v4 9.2 Critical because message misframing can silently corrupt a WebSocket stream.

How is CVE-2026-54466 fixed?

Version 0.7.5 adds a per-octet bound check — 'if (this._length > this._maxLength) return this.close();' — immediately after the length accumulation, so an overlong header closes the connection instead of desynchronizing the parser. Upgrade websocket-driver to 0.7.5 or later.

Which versions of websocket-driver are affected by CVE-2026-54466?

websocket-driver (faye/websocket-driver-node) versions < 0.7.5 are affected, via the Draft75/Draft76 (hixie-75/hixie-76) parser path. It is fixed in 0.7.5.

How can I reproduce CVE-2026-54466?

Download the verified script from this page and run it in an isolated environment against websocket-driver < 0.7.5. It sends a small crafted wire sequence that drives _length past _maxLength and shows a subsequent valid text frame being misframed, which the 0.7.5 fix prevents.
11 · References

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.