fix(groups): guard panel identity-name fallbacks against opaque LID restatements - #386
Open
detail-app[bot] wants to merge 1 commit into
Conversation
setkyar
force-pushed
the
detail/bug-fix/fix-groups-guard-panel-identity-name-fallbacks-aga-97fc3f
branch
from
September 12, 2026 14:04
6999074 to
57f6fcd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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") addedgetLidDisplayName(jid)as the terminal fallback of thedisplayNamechain ingetEnrichedGroupParticipants, 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
034backfillsmessages.sender_namewithsplit_part(sender_jid, '@', 1)for unmapped@lidsenders — i.e. the bare LID local-part digits. When that row is a quiescent LID member's newest group message,sender?.sender_namebecomes truthy and short-circuits the chain beforegetLidDisplayNameis 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 viagetSafeIdentityName.The
@lid/@hosted.lidJIDs 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 withsender_name: nullarrives), but the affected population — quiescent LID participants whose newest message was backfilled by migration034— 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-privategetSafeIdentityName(the guardgetContactDisplayName/getContactName/formatWhatsAppUsernamealready call internally). Behavior unchanged; it returnsnullfor 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— ingetEnrichedGroupParticipants, wrapped every identity-name candidate of thedisplayNamechain (custom_name,push_name,storedNameByJid,sender?.sender_name) with a per-membersafeName = (v) => getSafeIdentityName(v, jid)helper. A value that merely restates the member's opaque LID identity now returnsnulland falls through togetLidDisplayName(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
getSafeIdentityNamesuite inpackages/shared/src/contact.test.tscovering empty/whitespace, non-LID pass-through,@lid/@hosted.lidrejection 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.apps/api/src/services/group-sync.integration.test.tsagainst a real Postgres:masks LID participants whose backfilled sender_name carries the bare LID digits— reproduces the migration-034shape (16-digit@lid, 11-digit@lid, 8-digit@hosted.lid) and asserts each resolves toWhatsApp user (ID …XXXX), the per-row invariant holds, the panel agrees withgetContactDisplayName, a newer genuinesender_namestill shows, and a newer livesender_name: nullrow still masks. Verified the regression guard by reverting the fix — this test then fails withReceived: "6585719494172749", the exact reported leak.masks LID participants whose contact or stored name restates the opaque LID identity— defense-in-depth: apush_nameequal to the bare LID local part falls through to the mask. Reverting the fix makes it fail withReceived: "<bare digits>".@wateaminbox/sharedand@wateaminbox/databasebuild;tsc -p apps/api/tsconfig.json --noEmitandbun 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_namefor@lid/@hosted.lidsenders 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.