Without a per-user connection ceiling, a single authenticated user — or a compromised account — can open thousands of simultaneous WebSocket connections. Each consumes a file descriptor, memory, and CPU for keepalive processing. At scale this constitutes a low-cost denial-of-service attack that degrades or crashes the server for every other user. CWE-770 (Allocation of Resources Without Limits) captures exactly this failure mode, and ISO 25010 performance-efficiency requires that resource consumption be bounded under adversarial load.
High because an unbounded connection count per user can exhaust server file descriptors and memory, causing service degradation for all users.
Track open connections per user in a server-side Map and reject the handshake once the limit is reached. Cap at 3–5 for most community apps.
const userConnections = new Map<string, Set<string>>();
const MAX_CONNECTIONS_PER_USER = 3;
io.use((socket, next) => {
const userId = socket.userId;
const conns = userConnections.get(userId) ?? new Set();
if (conns.size >= MAX_CONNECTIONS_PER_USER) {
return next(new Error('Connection limit reached'));
}
conns.add(socket.id);
userConnections.set(userId, conns);
socket.on('disconnect', () => conns.delete(socket.id));
next();
});
The limit must be enforced in the middleware, not just configuration — a configured but unread value is not enforced.
ID: community-realtime.connection-management.per-user-connection-limits
Severity: high
What to look for: Count all connection tracking mechanisms: connection counters, maps, middleware that rejects new connections. Enumerate per-user limit enforcement points. Check for a maximum threshold (typically no more than 10 connections per user).
Pass criteria: The connection handler enforces a per-user maximum of at least 1 and no more than 10 connections. Exceeding the limit results in rejection or closure of excess connections. Report even on pass: "Per-user limit set to N connections."
Fail criteria: No per-user connection limit is implemented, or the limit is unenforced in code.
Do NOT pass when: A connection limit exists in configuration but the enforcement code never reads it — configured but unenforced limits are NOT a pass.
Skip (N/A) when: Never — connection limits are critical for stability.
Cross-reference: For rate limiting patterns and abuse prevention, the API Security Audit covers connection-level controls.
Detail on fail: "No per-user connection limit detected. A single user could theoretically open unlimited connections."
Remediation: Without per-user limits, a malicious or buggy client could exhaust server connection slots. Implement a limit:
const userConnections = new Map<string, Set<string>>();
const MAX_CONNECTIONS_PER_USER = 3;
io.use((socket, next) => {
const userId = socket.userId;
if (!userConnections.has(userId)) {
userConnections.set(userId, new Set());
}
const connections = userConnections.get(userId)!;
if (connections.size >= MAX_CONNECTIONS_PER_USER) {
return next(new Error('Too many connections'));
}
connections.add(socket.id);
socket.on('disconnect', () => {
connections.delete(socket.id);
});
next();
});