Skip to content

[Security] Any authenticated user can read every account's email address via permissive profiles RLS #1924

Description

@atul-upadhyay-7

Summary

The profiles table stores each user's email address, and its current SELECT policy lets any authenticated user read the full profile of every other user — including the email column.

Evidence

supabase/migrations/20260730000002_restrict_profiles_rls.sql:

CREATE POLICY "authenticated_users_can_view_profiles" ON public.profiles
  FOR SELECT TO authenticated
  USING (true);

This policy is OR'd with profiles_select_own from 20260729000000_add_profiles_rls_policy.sql. The legacy compat table public.users (created in 20260518000003_app_bootstrap_and_notifications.sql, which also stores email) has the same problem:

CREATE POLICY "Authenticated users can view users" ON public.users
  FOR SELECT TO authenticated USING (true);

The app itself is affected: src/hooks/useMessages.ts fetches .from("profiles").select("*").neq("id", currentUserId) — pulling the email of up to 100 other users on every chat page load.

Exploit

Any logged-in account can dump the entire user directory:

-- via PostgREST /rest/v1/profiles?select=email,name
select id, name, email from public.profiles;  -- RLS allows authenticated read of all rows
select email from public.users;               -- legacy table, same issue

Email addresses are PII and enable targeted phishing, password-reset attacks, and account-takeover attempts against confirmed registered users.

Impact

  • PII leak of every registered user's email address to any authenticated user.
  • Enables email enumeration and targeted phishing / social engineering.

Suggested Fix

  1. Revoke column-level SELECT on email from anon and authenticated roles so only the row owner (and service-role) can read it.
  2. Drop the permissive SELECT policy on the legacy public.users table (or revoke SELECT entirely — the app no longer queries it).
  3. Update client code that currently selects * from profiles (useMessages.ts, Profile.tsx, EditProfile.tsx) to select only the non-sensitive columns it actually renders.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions