Real-time transports retry delivery on network errors. Without deduplication, a message sent before a disconnect and resent after reconnect renders twice in the UI — the user sees duplicate chat lines, duplicate notifications, and potentially double-processed side effects. CWE-694 (Use of Multiple Resources with Duplicate Identifier) captures this integrity failure. The fix is straightforward but frequently omitted in AI-generated WebSocket code that copies the happy path without handling retry semantics.
High because network retries cause duplicate message renders and side effects without client-side deduplication, directly corrupting the conversation view.
Assign a client-generated ID to each outbound message and maintain a Set of received IDs on the client. Drop any message whose ID has already been processed.
// Receiving
const seen = new Set<string>();
socket.on('message', (msg: { id: string; content: string }) => {
if (seen.has(msg.id)) return;
seen.add(msg.id);
renderMessage(msg);
});
// Sending
const send = (content: string) => {
const id = nanoid(); // generated on the client
socket.emit('send_message', { id, content });
};
Bound the seen Set to a recent window (last N messages or TTL) if memory is a concern — an unbounded Set grows without limit in long-lived sessions.
ID: community-realtime.message-delivery.client-deduplication
Severity: high
What to look for: Count all client message handlers. For each, classify whether deduplication logic tracks received message IDs. Enumerate the deduplication mechanisms: Set, Map, or database-backed tracking.
Pass criteria: The client generates a unique ID for outgoing messages and tracks at least 1 set of received IDs to deduplicate. Duplicate receipts are ignored with 0 duplicate renders.
Fail criteria: No deduplication logic, or IDs are not client-generated.
Skip (N/A) when: Never — deduplication prevents duplicate renders from network retries.
Detail on fail: "Client message handler has no deduplication. Network retries could cause the same message to be rendered twice."
Remediation: Implement client-side deduplication:
const receivedMessageIds = new Set<string>();
socket.on('message', (msg: Message) => {
if (receivedMessageIds.has(msg.id)) {
return; // Ignore duplicate
}
receivedMessageIds.add(msg.id);
renderMessage(msg);
});
When sending, include a client-generated ID:
const sendMessage = (content: string) => {
const clientId = nanoid();
socket.emit('send_message', { clientId, content });
};