Forums Bug Reports Thread

Member Directory (/members) leaks real names + location that users set to private/friends-only, and makes them searchable

Patrick Bass · Jun 6 · 10 · 1 Locked
[Major] [High Priority] [Bug Fixed] [Always Reproduces]
🚀 OP Jun 6, 2026 5:11pm

Area: Account & identity (audit phase 1) · Surface: GET /members (MemberDirectoryController@index) · Dimension: security · Severity: major

OWASP A01:2021 Broken Access Control (function/data-level privacy bypass). Users can mark their real name (`profile_field_visibility['real_name']`) and location (`profile_field_visibility['country']` plus the `profile_display['show_location']` toggle) as `private` or `friends_only`. Those controls are enforced on /profile/{username} but are completely ignored by /members. Any authenticated member can therefore (1) see the real name and city/country of users who explicitly set them to private, and (2) enumerate/target users by location via the `?location=` filter and by real name via the `?q=` search (which matches first_name/last_name, MemberDirectoryController.php:27). This defeats a privacy control the product advertises in account settings — a user who set location to 'private' is still findable and viewable in the directory.

Evidence

MemberDirectoryController::index selects and exposes private fields with NO privacy gate:

platform/src/Controllers/MemberDirectoryController.php:38-44 — location filter applied to ALL users:
```php
if ($locationFilter !== '') {
    $escaped = str_replace(['\\', '%', '_'], ['\\\\', '\\%', '\\_'], $locationFilter);
    $where[] = '(u.country LIKE :loc1 OR u.city LIKE :loc2 OR u.state_province LIKE :loc3)';
```
platform/src/Controllers/MemberDirectoryController.php:59-67 — selects city/country/first_name/last_name for every member with no visibility check.

Template renders them unconditionally:
platform/templates/members/index.php:43 `$displayName = $dn($m);` (UsernameHelper::displayName returns first_name+last_name when set — src/Helpers/UsernameHelper.php:53-56)
platform/templates/members/index.php:44,57-58 renders city/country.

Contrast with the profile page, which DOES gate these exact fields:
platform/templates/profile/show.php:32-40 `$canSeeField` returns false for 'private', and only true for 'friends_only' when `$friendshipStatus === 'accepted'`; line 340 gates location on `($display['show_location'] || $showOwnerExtras) && $canSeeField('country')`; line 100 gates real name on `$canSeeField('real_name')`.

The directory controller never reads `profile_field_visibility` or `profile_display` (grep returns nothing). These are the same toggles users set via AccountController::updateDisplayPreferences (src/Controllers/AccountController.php:438-444, 'field_visibility_real_name'/'country' = public|friends_only|private).

Suggested fix. In MemberDirectoryController::index, decode each member's profile_field_visibility + profile_display and null out first_name/last_name and city/country for the viewer unless the viewer is the owner, an admin (role>=4), the field is 'public', or 'friends_only' with an accepted friendship — mirroring templates/profile/show.php's $canSeeField. For the location and name search/filter clauses, additionally constrain matches to rows the viewer is allowed to see those fields on (e.g. only filter by location for users whose effective country visibility is public, or who are friends of the viewer), so the search cannot be used as a private-data oracle. Best done by extracting the profile $canSeeField logic into a shared helper used by both surfaces.

Filed by the automated tenant-app audit (phase 1) and adversarially evidence-verified. Status: verified. Open — not yet actioned.


Patrick Bass
@mobieus

🚀 Jun 7, 2026 4:59am

Resolved — fixed and deployed. Commit faa14fc59739, shipped dev-first then to all tenants on 2026-06-06.

Added u.profile_field_visibility to the member SELECT in index(), then null out first_name/last_name (gated by 'real_name') and city/country (gated by 'country') unless the viewer is the owner, an admin (role>=4), the field is 'public', or 'friends_only' with an accepted friendship. Accepted friend IDs resolved once per page; raw visibility column stripped before render. Mirrors profile/show.php canSeeField semantics.

Status: fixed. Thread closed and locked.


Patrick Bass
@mobieus

Log in or register to reply to this thread.