What Is This Vulnerability
Even when Row Level Security is enabled on a table, the policies themselves may be misconfigured. An RLS bypass on SELECT occurs when the policy's USING clause is too broad, effectively granting read access to rows the user should not see. Common mistakes include using USING (true) which permits all reads, omitting the auth.uid() check, or writing conditions that always evaluate to true for the anonymous role.
This vulnerability is subtler than missing RLS entirely because the developer has taken the step of enabling RLS, giving a false sense of security. However, the policy logic itself fails to restrict access properly.
-- VULNERABLE: This policy allows ANY user to read ALL rows
CREATE POLICY "Allow read access"
ON orders FOR SELECT
USING (true);
Why It's Dangerous
An attacker with the anonymous key (or any authenticated user) can read every row in the table. Depending on the table contents this may expose:
- Personal identifiable information (PII) such as names, emails, and phone numbers
- Financial data including order amounts, billing details, and transaction history
- Internal application state such as admin flags, permission levels, and feature flags
- Other users' private content, messages, or documents
The data exfiltration can be performed silently using standard Supabase client calls, making it difficult to detect without query logging and anomaly detection.
How to Detect
Review your RLS policies for overly permissive conditions:
SELECT schemaname, tablename, policyname, permissive, cmd, qual
FROM pg_policies
WHERE schemaname = 'public'
AND cmd = 'SELECT';
Look for policies where the qual column contains true or lacks a reference to auth.uid(). AuditYourApp tests this by attempting to read data as an unauthenticated user and as an authenticated user without ownership of the rows. If data is returned that should be restricted, the policy is overly permissive.
How to Fix
Replace permissive SELECT policies with conditions that verify ownership or role:
-- Drop the overly permissive policy
DROP POLICY "Allow read access" ON orders;
-- Create a properly scoped policy
CREATE POLICY "Users can view own orders"
ON orders FOR SELECT
USING (auth.uid() = user_id);
-- For shared data, use team-based or role-based conditions
CREATE POLICY "Team members can view team orders"
ON orders FOR SELECT
USING (
team_id IN (
SELECT team_id FROM team_members
WHERE user_id = auth.uid()
)
);
If you intentionally need some rows to be publicly readable (e.g., published blog posts), scope the policy to only those rows:
CREATE POLICY "Public can view published posts"
ON posts FOR SELECT
USING (status = 'published');
Always test policies by querying as different roles (anon, authenticated, service_role) to verify the expected behavior.
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
Missing Row Level Security Policy
Tables without RLS are fully exposed to any user with the anon key, allowing unrestricted read and write access to all rows.
vulnerabilities
RLS Bypass: Unauthorized INSERT
Tables allow unauthenticated or cross-user inserts due to missing or overly permissive INSERT policies.
vulnerabilities
Authenticated User Data Leak
Authenticated users can read other users' data due to SELECT policies that do not enforce row-level ownership checks.