The core vulnerability in Hono's WebSocket upgrade handling isn't a logic error — it's a trap created by an underspecified API boundary. When ws.handleUpgrade receives a malformed Sec-WebSocket-Key, it neither throws, emits an error, nor emits 'connection'. It simply fails to emit anything, leaving the IncomingMessage orphaned in waiterMap with no contract requiring its eventual removal. Your code assumes handleUpgrade either succeeds or signals failure. That assumption is reasonable. The bug is that the library's actual behavior is silent omission under specific conditions correlated with malicious input.

The memory growth pattern is the clue: this requires an active attacker flooding malformed upgrade requests. Normal development with valid clients would never surface the issue. The cognitive load here is that WebSocket upgrade handling sits at an intersection of HTTP semantics, the WebSocket protocol spec, and library implementation details — each with its own error handling philosophy. You are expected to reason correctly about all three simultaneously, including failure modes that have never been documented.

For defenders: prioritize patching to Hono 2.0.10 or later. Beyond the immediate fix, examine any WebSocket upgrade routes in your codebase and verify they have explicit cleanup or timeout handling for the upgrade path — not just for successful upgrades, but for the orphaned state that occurs when the upgrade never completes. If you use ws directly or any framework that wraps handleUpgrade, audit for waiterMap-style patterns where pending operations accumulate without expiration. The absence of a cleanup contract in the underlying library means you must implement one yourself.

The uncomfortable question: was the original code obviously wrong in retrospect, or did it require specific knowledge of handleUpgrade's undocumented silent failure? The answer matters because this pattern — silent failure in async completion paths — recurs across Node.js ecosystems. The fix in 2.0.10 likely adds waiterMap cleanup through timeouts or abort handlers. That patches the symptom. The underlying issue is that the contract between ws.handleUpgrade and its callers never specified what happens on malformed input, and that specification gap exists in every library that inherits this behavior.