Skip to main content

On This Page

Securing Supabase: Preventing Data Leaks From Misconfigured Row Level Security

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Supabase RLS: The Hidden Danger (And How to Find It Before Hackers Do)

Supabase leverages PostgreSQL Row Level Security (RLS) to manage data access, yet many developers inadvertently leave tables wide open. Misconfiguring RLS or using ‘USING (true)’ policies allows anyone with a public anon key to extract entire databases via simple curl commands.

Why This Matters

In theory, enabling RLS denies access by default, creating a secure-by-design environment. However, the technical reality is that Supabase’s dashboard marks tables as ‘RLS enabled’ even when no policies exist or when overpermissive rules are applied—often by AI coding tools prioritizing functionality over security. This gap between perceived security and actual policy implementation creates a significant attack surface where sensitive user data is accessible through standard public keys found in client-side bundles.

Key Insights

  • AI-generated code from tools like Cursor, Lovable, and Bolt often defaults to ‘USING (true)’ policies to ensure development tests pass, inadvertently creating production vulnerabilities.
  • The ‘anon’ key is public by design, meaning any policy that doesn’t explicitly check ‘auth.uid()’ or ‘authenticated’ roles can be exploited by unauthenticated external requests.
  • Storage buckets marked as ‘public’ in Supabase allow anyone with the URL to list and access files, regardless of folder path complexity.
  • The ‘service_role’ key completely bypasses RLS and should never be exposed in client-side code, yet it is frequently misused for frontend simplicity.
  • Continuous monitoring tools like AEGIS and FounderScan perform 12+ security checks to identify overpermissive Postgres policies automatically.

Working Examples

Enabling RLS without defining policies can lead to misleading security states.

ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;
-- Result: NO rows are returned for authenticated users
-- BUT: anon users can still read everything if you have a permissive grant

How an attacker extracts data using only the public anon key.

curl 'https://YOUR_PROJECT.supabase.co/rest/v1/user_profiles?select=*' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"

The correct pattern for secure, user-scoped RLS policies.

CREATE POLICY "Users can read own profile"
ON user_profiles FOR SELECT
TO authenticated
USING (auth.uid() = user_id);

CREATE POLICY "Users can update own profile"
ON user_profiles FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);

Practical Applications

  • User Profile Isolation: Implement ‘auth.uid() = user_id’ checks for all SELECT and UPDATE operations to ensure users cannot view or modify peer data.
  • Storage Security: Avoid setting ‘public = true’ on storage buckets containing sensitive documents; use authenticated-only access policies instead.
  • Policy Auditing: Use the ‘pg_policies’ system table to identify ‘qual = (true)’ flags which indicate dangerous, wide-open access rules.
  • Server-Side Secret Management: Restrict ‘service_role’ keys to Edge Functions or backend environments to prevent RLS bypass vulnerabilities.

References:

Continue reading

Next article

The 429 That Poisoned Every Fallback: AI Agent Reliability Risks

Related Content