What Is This Vulnerability
An authenticated data leak occurs when RLS SELECT policies allow any authenticated user to read all rows in a table, rather than restricting access to rows they own. The policy correctly excludes anonymous users but fails to differentiate between authenticated users. This means User A can read User B's data simply by being logged in.
-- VULNERABLE: Any logged-in user can read ALL rows
CREATE POLICY "Authenticated can read"
ON user_profiles FOR SELECT
TO authenticated
USING (true);
This is a common pattern when developers focus on blocking anonymous access but forget that authenticated users also need row-level restrictions. It creates a false sense of security because anonymous access is properly blocked.
Why It's Dangerous
In a multi-tenant application, this vulnerability allows any registered user to:
- Enumerate all users: Read every profile, email address, and personal detail in the system
- Access private content: View other users' private documents, messages, or uploads
- Harvest competitive data: Extract business data, pricing, or customer lists
- Facilitate social engineering: Use leaked personal information for targeted phishing attacks
Since the attacker must be authenticated, the barrier is slightly higher than public access. However, account creation is usually trivial (sign up with any email), making this only a minor obstacle. In many Supabase projects, email verification is not enforced, so an attacker can create throwaway accounts instantly.
// Any authenticated user can dump all profiles
const { data } = await supabase
.from('user_profiles')
.select('id, email, full_name, phone, address');
// Returns ALL users' data, not just the caller's
How to Detect
AuditYourApp tests this by creating two test users and attempting to read data owned by User A while authenticated as User B. If the query returns User A's rows, the policy is overly permissive.
You can manually check by inspecting policies:
SELECT tablename, policyname, cmd, qual, roles
FROM pg_policies
WHERE schemaname = 'public'
AND cmd = 'SELECT'
AND roles @> ARRAY['authenticated']
AND qual = 'true';
Any SELECT policy for the authenticated role with a qual of true is a likely finding, unless the table is intentionally shared (e.g., a public directory).
How to Fix
Add ownership checks to SELECT policies:
DROP POLICY "Authenticated can read" ON user_profiles;
-- Users can only read their own profile
CREATE POLICY "Users can read own profile"
ON user_profiles FOR SELECT
TO authenticated
USING (auth.uid() = id);
For tables where some data should be shared (e.g., a user directory showing names but not emails), create separate policies or use column-level security:
-- Create a public view with limited columns
CREATE VIEW public_profiles AS
SELECT id, display_name, avatar_url
FROM user_profiles;
-- Or use multiple policies with different scopes
CREATE POLICY "Users can read own full profile"
ON user_profiles FOR SELECT
TO authenticated
USING (auth.uid() = id);
CREATE POLICY "Users can read others display info"
ON user_profiles FOR SELECT
TO authenticated
USING (visibility = 'public');
Always consider whether the query pattern your client uses actually needs all rows or just the current user's rows. Structure your RLS to match the minimum access required.
Scan your app for this vulnerability
AuditYourApp automatically detects security misconfigurations in Supabase and Firebase projects. Get actionable remediation in minutes.
Run Free ScanRelated
vulnerabilities
RLS Bypass: Unauthorized SELECT
Overly permissive SELECT policies allow users to read data they should not have access to, exposing sensitive information.
vulnerabilities
Authenticated Cross-User Write Access
Authenticated users can modify or delete other users' data due to write policies lacking ownership checks.
vulnerabilities
Public Table Read Access
Tables are readable by anonymous users through the Supabase API, potentially exposing sensitive data to unauthenticated visitors.