high · 7.5CVE-2026-59724Aug 31, 2026

CVE-2026-59724: engine.io WebTransport Prototype Lookup DoS

Rohit Hatagale
AI Security Researcher, SecureLayer7

Engine.IO servers with WebTransport enabled can be crashed by an unauthenticated attacker who sends a session ID of __proto__, tricking the server into reading a prototype property instead of a real…

Packageengine.io
Ecosystemnpm
Affected>= 6.5.0, < 6.6.7
Fixed in6.6.7
CVE-2026-59724: engine.io WebTransport Prototype Lookup DoS

The problem

Engine.IO versions 6.5.0 through 6.6.6 introduced WebTransport support. During the WebTransport upgrade handshake, the server reads a session ID (SID) from the client and looks it up with a plain bracket access on the internal this.clients object.

Because JavaScript bracket access traverses the prototype chain, a SID of __proto__ resolves to Object.prototype itself instead of a real client entry. The server then tries to call methods on that object, throws a TypeError, and, because the failure is inside an async context, the Node.js process terminates with an unhandled Promise rejection.

No authentication is required.

Proof of concept

A working proof-of-concept for CVE-2026-59724 in engine.io, with the exact payload below.

python
# 1. Start a normal polling session to get a real EIO=4 handshake (optional context)
# 2. Open a WebTransport connection to the engine.io endpoint
# 3. On the first bidirectional stream, send the WebTransport upgrade datagram
#    with sid set to __proto__ (per the Engine.IO WebTransport upgrade framing)

# Minimal Python pseudocode showing the crafted upgrade payload sent over
# the WebTransport stream after the QUIC/HTTP3 connection is established:

import json

# Engine.IO WebTransport upgrade: client sends a packet type 2 (ping probe)
# preceded by the session-ID negotiation data containing the poisoned SID.
# The server parses this JSON and passes sid directly to: this.clients[sid]

upgrade_data = json.dumps({"sid": "__proto__", "upgrades": [], "pingInterval": 25000, "pingTimeout": 20000})

# Sending upgrade_data over the WebTransport bidi stream causes the server to
# evaluate: this.clients["__proto__"] -> Object.prototype (not a real client)
# -> TypeError thrown in async handler -> unhandled rejection -> process exit

The root cause (CWE-20: Improper Input Validation) is a plain bracket property lookup: this.clients[sid]. In JavaScript, bracket access on a plain object does not restrict itself to own properties, so clients["__proto__"] returns Object.prototype rather than undefined.

The patch (commit 1fa1f46) adds a hasOwn() helper that calls Object.prototype.hasOwnProperty.call(obj, key) and gates the lookup so any key that is not an own property of clients is rejected before any further processing. This is the standard defense against prototype-chain key injection in Node.js servers.

The fix

Upgrade to engine.io 6.6.7 or any later release. If you use Socket.IO, update to a Socket.IO release that bundles engine.io 6.6.7+. If an immediate upgrade is not possible, remove webtransport from the transports array in your server config (WebTransport is opt-in and not enabled by default, so most deployments are not affected).

Reported by Damien Arrachequesne (darrachequesne).

References: [1][2][3][4][5]

Related research