fix(messages,privacy): stop the People Picker from republishing email-derived handles - #854
Open
catomean wants to merge 1 commit into
Open
fix(messages,privacy): stop the People Picker from republishing email-derived handles#854catomean wants to merge 1 commit into
catomean wants to merge 1 commit into
Conversation
…-derived handles
Visitor feedback on /messages: the New Message picker's default
suggestion list showed accounts like "georgy.butaev+ocauth1
@georgy.butaev+ocauth1" — real email local-parts published as public
handles, visible to any logged-in user. This is the leak fixed at the
two known signup paths in 20260826130000_stop_deriving_usernames_from_email.sql
and ProfileServerService.ensureProfile() — but not every account is
through the (deliberately manual, not-automatic) backfill script yet,
and two more write paths still minted the same leak independently:
- scripts/db/setup-db.ts hardcodes a sign-in as butaeff@gmail.com and
upserts username: email.split('@')[0] on every run — almost
certainly the actual source of the "+ocauth1"/"+ocauth2" test
accounts in the report (butaeff is one of them).
- scripts/db/reset-user-password.js upserts username: email verbatim
(the full address, not just the local part) for its fixture account.
Both now use the same neutral-username shape as the fixed paths
(neutralUsernameFor in the .ts script; the equivalent inlined in the
plain .js one, which can't import it).
For accounts already leaked and not yet backfilled: added
isEmailDerivedHandle() (src/config/public-directory.ts, same predicate
and .invalid exception as the SQL side's count_email_derived_usernames())
and wired it into GET /api/profiles to hide a still-leaking handle from
the *default* (no search term) suggestion list — the surface this was
reported from. An explicit search still finds the person; this isn't a
substitute for renaming their handle, which needs
scripts/rename-email-derived-usernames.sql run against production by
someone with DB access (noted in the session handoff, not in scope for
this dispatch — no live Supabase credentials in this sandbox).
Also: NewConversationModal's picker row showed name and @username
duplicated whenever a profile has no display name (title already falls
back to the handle) — pure noise, worse on narrow screens where both
lines truncate. Now the @handle line only renders when it adds
information beyond the title.
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.
Summary
Visitor feedback on
/messages: the New Message picker's default suggestion list showed accounts likegeorgy.butaev+ocauth1 @georgy.butaev+ocauth1— real email local-parts published as public handles, visible to any logged-in user.This exact class of leak was already fixed at the two known write paths (
handle_new_user()trigger,ProfileServerService.ensureProfile()— seesupabase/migrations/20260826130000_stop_deriving_usernames_from_email.sql), with an existing, deliberately-manual backfill script (scripts/rename-email-derived-usernames.sql). But:Two more write paths still minted the same leak, independently of both fixed ones, found by an exhaustive sweep of every place a
profilesrow gets created or itsusername/nameset:scripts/db/setup-db.tshardcodes a sign-in asbutaeff@gmail.comand upsertsusername: email.split('@')[0]on every run — almost certainly the actual source of the+ocauth1/+ocauth2test accounts in the report (butaeffis literally one of them).scripts/db/reset-user-password.jsupsertsusername: emailverbatim (the full address) for its fixture account.Both now use the same neutral-username shape as the already-fixed paths.
Accounts already leaked and not yet backfilled are still visible today. Added
isEmailDerivedHandle()(src/config/public-directory.ts) — same predicate and.invalidsystem-account exception as the SQL side'scount_email_derived_usernames()— and wired it intoGET /api/profilesto hide a still-leaking handle from the default (no search term) suggestion list, the exact surface this was reported from. An explicit search still finds the person — hiding someone from a search for their exact name would be an availability regression, not a privacy fix. This is a mitigation, not a substitute for actually renaming the handle, which needs the existing (already-correct, already.invalid-aware)scripts/rename-email-derived-usernames.sqlrun against production by someone with DB credentials — out of scope for this dispatch, noted in the session handoff (this sandbox has no live Supabase credentials).Secondary complaint ("double-truncated noise"):
NewConversationModal's picker row showed name and@usernameduplicated whenever a profile has no display name (the title already falls back to the handle). Now the@handleline only renders when it adds information beyond the title.Deliberately left out of scope
The same investigation surfaced two more email-derived-display-name fallbacks (
src/services/timeline/utils/post-composer.ts's optimistic post event,src/components/timeline/PostComposerMobile.tsx,src/components/ui/UserProfileDropdown.tsx) — all client-side, self-view only (only the account's own owner ever sees their own optimistic post or their own account-menu label; not a third-party exposure), so not the reported bug. Noted in the handoff as a follow-up, not fixed here per "address exactly this feedback."Test plan
npx tsc --noEmit— cleannpx eslinton all changed files — 0 errorsisEmailDerivedHandle()in__tests__/unit/config/public-directory.test.ts: flags a still-leaking handle, case-insensitive, never flags a.invalidsystem account (the exact bug that took the Cat's own handle down for two days — see20260828070000_system_accounts_keep_their_handles.sql), false with nothing to comparenpm run test:unit(2642 tests) and full pre-push suite — greenGET /api/profiles's new filter end-to-end against live data — this sandbox has no Supabase credentials, so the query never actually executes here. Verified by code review:emailis selected only for the server-side filter and stripped via.map(({email, ...rest}) => rest)before the response is ever serialized — it never reaches the client.🤖 Generated with Claude Code