high · 7.5Jul 24, 2026

@anephenix/hub WebSocket RPC Waiter Resource Exhaustion

Shubham Kandhare
Security Engagement Manager, SecureLayer7

Any unauthenticated attacker can crash an @anephenix/hub server by opening many WebSocket connections and never responding, causing the server to accumulate unlimited background timers and memory…

Package@anephenix/hub
Ecosystemnpm
Affected< 0.2.16
Fixed in0.2.16
@anephenix/hub WebSocket RPC Waiter Resource Exhaustion

The problem

Every incoming WebSocket connection triggers a server-side RPC call to request a client ID via get-client-id. Internally, rpc.send pushes a request object onto this.requests and starts a setInterval that polls every 10 ms waiting for a reply.

The interval is only cleared when a matching response arrives. There is no timeout, and the socket close handler does not cancel pending RPC requests. An attacker who opens connections and never replies causes one leaking timer and one heap object to accumulate per connection, permanently.

Proof of concept

A working proof-of-concept for this issue in @anephenix/hub, with the exact payload below.

bash
node --input-type=module - <<'EOF'
import Hub from './dist/esm/index.js';
import { WebSocket } from 'ws';

const port = 8766;
const hub = new Hub({ port });
hub.listen();
const ws = new WebSocket(`ws://localhost:${port}`);

// Wait for the server's get-client-id RPC message, then close without replying
await new Promise((resolve) => ws.once('message', resolve));
ws.close();
await new Promise((resolve) => setTimeout(resolve, 300));

console.log(JSON.stringify({
  serverClients:      hub.wss.clients.size,   // 0  — socket is gone
  pendingRpcRequests: hub.rpc.requests.length, // 1  — leak confirmed
}));

hub.server.close();
process.exit(0);
EOF

The root cause is CWE-400 (Uncontrolled Resource Consumption). In src/lib/rpc.ts, waitForReply starts a setInterval(..., 10) whose only exit path is a matching response arriving in responses[]. No timeout branch exists, and the close event listener registered in loadDefaultConnectionEventListeners only calls pubsub.unsubscribeClientFromAllChannels, so neither the interval reference nor the this.requests entry is ever cleaned up when the socket disconnects.

The patch in v0.2.16 (commits 67260d2 and 931576d) wires a close handler into the RPC layer that calls clearInterval on any in-flight waiter and splices the stale entry out of this.requests when its associated WebSocket closes. This closes the gap between socket lifetime and RPC waiter lifetime.

The fix

Upgrade @anephenix/hub to version 0.2.16 or later. No configuration change is required; the fix is applied automatically by the updated close handler in the RPC layer.

Reporter not attributed.

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

Related research