Skip to content

[Detail Bug] Groups: Group info panel shows raw LID digits for some participants due to unguarded sender_name fallback #382

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_ee14aa77-b24a-40b2-b22d-66bd31931f4a/bugs/bug_996785d0-a4d7-4308-b731-1757ff807073

Introduced in #359 by @setkyar on Sep 10, 2026

Summary

  • Context: getEnrichedGroupParticipants in apps/api/src/services/group.service.ts builds the per-member displayName rendered on the group info panel. Commit 3403183 ("fix(groups): mask unnamed LID participants in group info panel") added getLidDisplayName(jid) so that @lid/@hosted.lid members resolve to the privacy-safe label WhatsApp user (ID …XXXX) instead of their opaque identity digits.
  • Bug: The mask only fires at the terminal fallback. The earlier identity-name terms of the displayName chain are read raw — none routed through getSafeIdentityName, the LID-leak guard that the sibling profile path (getContactDisplayName) applies to the very same fields. The demonstrated leak is the sender?.sender_name term: migration 034 backfills messages.sender_name with the bare LID local-part digits for unmapped LID senders, and the panel chain short-circuits on that value, never reaching getLidDisplayName.
  • Actual vs. expected: For an @lid member whose newest group message was backfilled by migration 034, the panel shows the bare LID digits. The commit's own per-row invariant formatPhoneLikeText(displayName) === formatPhoneLikeText(jid) is violated. The same member's opened contact profile produces WhatsApp user (ID …XXXX).
  • Impact: A privacy regression that leaks the opaque LID token the commit was written to hide. The leak window is bounded: migration 034 backfills sender_name with split_part(sender_jid, '@', 1) for unmapped LID senders, but the live message writer's terminal fallback is extractPhoneFromJid(lidJid) which returns null for @lid (message-handlers.ts:270). So the leak heals the moment any post-migration live message arrives from that member.

Code with Bug

// apps/api/src/services/group.service.ts — getEnrichedGroupParticipants (line 386)
const displayName =
  contact?.custom_name ||
  contact?.push_name ||
  storedNameByJid.get(jid) ||
  sender?.sender_name ||                   // <-- BUG 🔴 raw backfilled LID digits short-circuit before LID mask
  (phoneNumber ? `+${phoneNumber}` : null) ||
  getLidDisplayName(jid) ||                 // intended mask — unreachable when sender?.sender_name is truthy
  "Unknown participant";

Explanation

  • For @lid/@hosted.lid JIDs, phoneNumber is null, so the chain relies on earlier fallbacks.
  • Migration 034 can persist messages.sender_name as the LID local-part digits for unmapped LID senders; when that row is the most recent message for the participant, sender?.sender_name becomes truthy and getLidDisplayName(jid) is never reached.
  • This diverges from the profile rendering path (getContactDisplayName), which applies getSafeIdentityName(value, contact.jid) to reject “names” that simply repeat an LID identity.

Codebase Inconsistency

  • Profile path (packages/shared/src/contact.ts) guards these same identity-name fields via getSafeIdentityName, but the group panel path does not.
  • Another guard exists (resolveMemberPushName in apps/api/src/services/group-participant-contacts.ts) that explicitly rejects candidates repeating the member’s opaque identity; the group panel displayName chain applies neither.

Recommended Fix

  • Export getSafeIdentityName from @wateaminbox/shared (or add an equivalent exported helper) and wrap the panel’s identity-name candidates (custom_name, push_name, storedNameByJid, sender?.sender_name) so LID-identity restatements fall through to getLidDisplayName(jid).
  • Consider a follow-up cleanup migration to re-backfill messages.sender_name for @lid/@hosted.lid senders with a digit-repetition filter, so the opaque digits are not persisted.

History

This bug was introduced in commit 3403183. The change ("fix(groups): mask unnamed LID participants in group info panel") set out to hide opaque LID tokens from the group info panel by adding getLidDisplayName(jid) as the terminal fallback of the displayName chain, replacing the previously-leaky jid.split("@")[0]. The bug slipped in because the fix only replaced the terminal fallback — it did not route the earlier identity-name terms (sender?.sender_name, and custom_name/push_name/storedNameByJid) through getSafeIdentityName, the LID-leak guard that the sibling profile path (getContactDisplayName) already applied to those same fields. The accompanying integration test only constructed LID participants with no messages rows, so sender?.sender_name was never exercised and the gap went undetected.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions