Skip to content

Security: stop /getEventUsers leaking attendee PII#505

Merged
KishParikh13 merged 1 commit into
v2from
fix/geteventusers-pii-leak
Jun 17, 2026
Merged

Security: stop /getEventUsers leaking attendee PII#505
KishParikh13 merged 1 commit into
v2from
fix/geteventusers-pii-leak

Conversation

@KishParikh13

Copy link
Copy Markdown
Collaborator

Problem

POST /getEventUsers is public (no auth) and returned the full userRowToApi() object for every attendee — leaking email, phoneNumber, dateOfBirth, and suspension internals to anyone who knows (or enumerates) an event ID. It powers the attendee avatar list, which only needs name + photo.

Fix

  • New display-only mapper userRowToAttendee(){ id, firstName, lastName, profileImage }, used for the public attendee list.
  • Also drops username, because in this app username currently holds the login email — exposing it would leak the email anyway. (That username==email coupling is a separate, larger cleanup; this PR is safe regardless of it.)
  • Frontend: the 5 attendee-display sites fall back to 'Member' / initials now that username isn't in the public payload (no crash on missing field).
  • Coordinators still get full emails + guest RSVPs via the gated GET /events/:id/roster (separate PR Coordinator attendee roster + CSV export on the event page #504).

Test plan

  • npm run typecheck (backend + frontend) clean
  • backend tests — 432 passing, incl. a new assertion that the public list has no email/username/phone/DOB
  • frontend tests — 197 passing
  • Manual: open an event with attendees → avatar list still shows names/photos; network response for /getEventUsers contains only id/firstName/lastName/profileImage

Related

Surfaces the broader username == email data-model issue (login uses email, stored in username, with a parallel email column) — flagged for a dedicated decision/migration.

POST /getEventUsers is public (no auth) and returned the full
userRowToApi() shape for every attendee — exposing email, phoneNumber,
dateOfBirth, and suspension internals to anyone who knows an event ID.

Add a display-only userRowToAttendee() mapper { id, firstName, lastName,
profileImage } and use it for the public attendee list. It also drops
`username` because in this app username currently holds the login email,
so exposing it would leak the email too (see the username/email
decoupling follow-up).

Coordinators still get emails + guest RSVPs via the gated
GET /events/:id/roster.

- backend: userRowToAttendee() in types.ts; /getEventUsers uses it
- frontend: attendee display falls back safely to 'Member' now that
  username isn't returned (5 mapping sites in useApiData)
- tests: assert the public list has no email/username/phone/DOB

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying chinmaya-janata with  Cloudflare Pages  Cloudflare Pages

Latest commit: 94cd62d
Status: ✅  Deploy successful!
Preview URL: https://bc4e4843.project-janatha.pages.dev
Branch Preview URL: https://fix-geteventusers-pii-leak.project-janatha.pages.dev

View logs

@KishParikh13 KishParikh13 merged commit ccbe257 into v2 Jun 17, 2026
3 checks passed
KishParikh13 added a commit that referenced this pull request Jun 17, 2026
Resolves #514's intent without reversing the #505/#504 privacy model:

- Public attendee COUNT (people_attending) now = account attendees +
  non-upgraded guest RSVPs, via a shared recomputeAttendeeCount() called
  from addEventAttendee/removeEventAttendee/upgradeGuestRsvps/createGuestRsvp.
  So "X going" stays accurate including guests — no PII, just a number.
- /getEventUsers (the WHO list) is now gated: authMiddleware (401) + must be
  an attendee, the creator, or an admin (else 403). Still PII-free; the
  separate gated /events/:id/roster remains the emails+guests view.
- Frontend useEventDetail: count comes from the event's peopleAttending (not
  the account-list length); a gated list no longer errors the screen; RSVP
  toggles the count optimistically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant