Presence derived from client-controlled heartbeats can be forged: a disconnected or banned user can keep pinging to appear online, and a connected user can simply stop sending heartbeats to appear offline. CWE-287 (Improper Authentication) and OWASP A07 identify any system that trusts client-reported state for security decisions as broken. In a community platform, forged presence undermines moderation dashboards, violates user expectations about who can see them, and breaks features like "last seen" timestamps that users rely on to make trust decisions.
Critical because client-controlled presence signals can be forged indefinitely, allowing banned users to appear online and violating OWASP A07 authentication integrity.
Derive presence exclusively from server-observed connection events — connection and disconnect. Never update presence in response to a client-emitted event.
const presence = new Map<string, 'online' | 'offline'>();
io.on('connection', (socket) => {
presence.set(socket.userId, 'online');
io.emit('presence_update', { userId: socket.userId, status: 'online' });
socket.on('disconnect', () => {
presence.set(socket.userId, 'offline');
io.emit('presence_update', { userId: socket.userId, status: 'offline', lastSeen: Date.now() });
});
// Never handle a 'set_status' event from the client
});
If users need a voluntary "Do Not Disturb" status, store it as a separate preference field — it must not override the authoritative online/offline derived from connection state.
ID: community-realtime.presence.server-computed-presence
Severity: critical
What to look for: Enumerate all presence tracking mechanisms. For each, classify whether presence is derived from server-side connection state or from client-side heartbeats. Count the mechanisms that are server-authoritative.
Pass criteria: Presence is derived from server-side connection state. A user is marked online only if they have at least 1 active, authenticated WebSocket/SSE connection. 100% of presence updates must be server-computed. Report even on pass: "Presence computed server-side via [mechanism]."
Fail criteria: Presence is based on client-generated heartbeats or messages that the client controls.
Do NOT pass when: Server tracks connection state but allows clients to override it via a custom "set_status" event — client-overridable presence is NOT a pass.
Skip (N/A) when: Never — server-computed presence is critical for correctness.
Detail on fail: "Presence tracked via client heartbeat. A client could send heartbeats indefinitely to appear online after disconnection."
Remediation: Compute presence from server connection state:
const userPresence = new Map<string, { userId: string; status: 'online' | 'offline'; lastSeen: number }>();
io.on('connection', (socket) => {
const userId = socket.userId;
userPresence.set(userId, { userId, status: 'online', lastSeen: Date.now() });
io.emit('presence_update', { userId, status: 'online' });
socket.on('disconnect', () => {
userPresence.set(userId, { userId, status: 'offline', lastSeen: Date.now() });
io.emit('presence_update', { userId, status: 'offline' });
});
});
// Never update presence based on client-provided heartbeats