# Duplicate messages deduplicated via client-generated ID before rendering

- **Pattern:** `ab-000741` (`community-realtime.message-delivery.client-deduplication`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000741
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000741)

## Why it matters

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.

## Severity rationale

High because network retries cause duplicate message renders and side effects without client-side deduplication, directly corrupting the conversation view.

## Remediation

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.

```ts
// 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.

## Detection

- **ID:** `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:

  ```ts
  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:

  ```ts
  const sendMessage = (content: string) => {
    const clientId = nanoid();
    socket.emit('send_message', { clientId, content });
  };
  ```

## External references

- cwe CWE-694

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000741
