Skip to content

fix(groups): guard panel identity-name fallbacks against opaque LID restatements - #386

Open
detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-groups-guard-panel-identity-name-fallbacks-aga-97fc3f
Open

detail-app[bot] wants to merge 1 commit into
mainfrom
detail/bug-fix/fix-groups-guard-panel-identity-name-fallbacks-aga-97fc3f

Conversation

@detail-app

@detail-app detail-app Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Problem

The group info panel leaked opaque LID identity digits for some participants. Commit 3403183 ("fix(groups): mask unnamed LID participants in group info panel") added getLidDisplayName(jid) as the terminal fallback of the displayName chain in getEnrichedGroupParticipants, but the earlier identity-name terms (sender?.sender_name, custom_name, push_name, stored WhatsApp names) were read raw — none routed through the LID-leak guard.

The demonstrated leak: migration 034 backfills messages.sender_name with split_part(sender_jid, '@', 1) for unmapped @lid senders — i.e. the bare LID local-part digits. When that row is a quiescent LID member's newest group message, sender?.sender_name becomes truthy and short-circuits the chain before getLidDisplayName is reached, so the panel shows the opaque digits. This violates the commit's own per-row invariant (formatPhoneLikeText(displayName) === formatPhoneLikeText(jid)) and diverges from the sibling profile path (getContactDisplayName), which already guards these same fields via getSafeIdentityName.

The @lid/@hosted.lid JIDs have no phone number, so the chain relies on the earlier fallbacks — making the leak reachable for any digit length. The window is bounded (it self-heals once a post-migration live message with sender_name: null arrives), but the affected population — quiescent LID participants whose newest message was backfilled by migration 034 — leaks the very token the commit was written to hide.

Closes #382

Approach

Made the panel path apply the same LID-leak guard the profile path already uses, so opaque-LID restatements fall through to the privacy-safe label:

  • packages/shared/src/contact.ts — exported the previously module-private getSafeIdentityName (the guard getContactDisplayName/getContactName/formatWhatsAppUsername already call internally). Behavior unchanged; it returns null for any name that equals the whole LID JID, the local part, or whose digits match the LID local-part digits, and is a no-op for non-LID JIDs.
  • apps/api/src/services/group.service.ts — in getEnrichedGroupParticipants, wrapped every identity-name candidate of the displayName chain (custom_name, push_name, storedNameByJid, sender?.sender_name) with a per-member safeName = (v) => getSafeIdentityName(v, jid) helper. A value that merely restates the member's opaque LID identity now returns null and falls through to getLidDisplayName(jid). Non-LID members are unaffected (the guard passes names through unchanged for @s.whatsapp.net), so the +<phoneNumber> fallback and existing named contacts behave exactly as before.

This mirrors the fix the profile path already had, restoring agreement between the two render paths on identical input.

Testing

  • Unit tests — added a getSafeIdentityName suite in packages/shared/src/contact.test.ts covering empty/whitespace, non-LID pass-through, @lid/@hosted.lid rejection of bare digits, whole-JID, device-suffixed local part, phone-shaped and differently-punctuated restatements, and real-name pass-through. Full shared suite: 28 pass / 0 fail.
  • Integration tests — added two cases to apps/api/src/services/group-sync.integration.test.ts against a real Postgres:
    • masks LID participants whose backfilled sender_name carries the bare LID digits — reproduces the migration-034 shape (16-digit @lid, 11-digit @lid, 8-digit @hosted.lid) and asserts each resolves to WhatsApp user (ID …XXXX), the per-row invariant holds, the panel agrees with getContactDisplayName, a newer genuine sender_name still shows, and a newer live sender_name: null row still masks. Verified the regression guard by reverting the fix — this test then fails with Received: "6585719494172749", the exact reported leak.
    • masks LID participants whose contact or stored name restates the opaque LID identity — defense-in-depth: a push_name equal to the bare LID local part falls through to the mask. Reverting the fix makes it fail with Received: "<bare digits>".
    • All 8 pre-existing group-sync tests stay green (no regression).
  • Adjacent suites — group-administration-sync, group-member-naming-scope, and group-participant-contacts: 44 pass / 0 fail.
  • Full API suite — 925 pass / 0 fail / 559 skip (unit); a controlled baseline comparison of the full suite with integration tests enabled showed identical pass count before and after the change, confirming no regressions from this fix.
  • Build, typecheck, lint@wateaminbox/shared and @wateaminbox/database build; tsc -p apps/api/tsconfig.json --noEmit and bun run typecheck (api + web) are clean; Biome reports no issues on the changed files; Knip reports no unused exports.

Operational / security impact

This is a privacy fix: it stops the opaque LID token (the identity WhatsApp issues to hide a member's phone number) from being displayed on the group info panel for the affected quiescent participants. The fix is read-only — it changes display resolution only and does not alter any persisted data, the contact/stored-name writers, or the message writer. A follow-up cleanup migration to re-backfill messages.sender_name for @lid/@hosted.lid senders with a digit-repetition filter (so the digits are not persisted in the first place) is recommended as defense-in-depth but is out of scope here; the read-time guard closes the regression for all existing rows immediately.


Automatic Fixes PRs can be configured here.

@detail-app
detail-app Bot requested a review from setkyar September 11, 2026 21:57
@setkyar
setkyar force-pushed the detail/bug-fix/fix-groups-guard-panel-identity-name-fallbacks-aga-97fc3f branch from 6999074 to 57f6fcd Compare September 12, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant