Supabase Security

Supabase Security Incidents

RLS bypasses, configuration mistakes, and common security issues in Supabase-powered applications.

Not Supabase platform bugs. These are the misconfigurations that developers (and AI tools) introduce when building on Supabase, leading to data exposure and security breaches.

VAS automatically detects Supabase misconfigurations, exposed keys, and RLS issues

About Supabase Keys

Supabase anon keys are designed to be public. They are not secrets. Security in Supabase is enforced by Row Level Security (RLS) policies, not by hiding the anon key. However, the service_role key is a secret and must never appear in client-side code. It bypasses all RLS policies and provides full database access.

The Supabase Security Challenge

Supabase has become the backend of choice for vibe-coded applications. Its combination of PostgreSQL, authentication, real-time subscriptions, and storage makes it incredibly powerful for rapid development. Tools like Lovable, Bolt.new, and Cursor generate Supabase-backed applications by default, and the Supabase free tier makes it accessible to every developer.

But Supabase's power comes with responsibility. Unlike traditional APIs where the backend controls all data access, Supabase exposes the database directly to the client through PostgREST. This means the security boundary is not your API layer; it is your RLS policies. If those policies are missing, permissive, or incorrectly configured, your database is effectively public.

The rise of AI coding tools has dramatically increased the number of Supabase applications with security issues. These tools generate functional code quickly, but they consistently struggle with Supabase security configuration. They create tables without enabling RLS, use service role keys in client code, write permissive policies, and skip storage bucket security. The result is a wave of applications that work perfectly but expose all their data to anyone who knows how to use the Supabase client library.

This page documents the most common Supabase security incidents and misconfigurations, with specific code examples showing both the vulnerable patterns and the correct implementations.

Supabase Misconfiguration by the Numbers

73%
of vibe-coded Supabase apps have RLS issues
1 in 5
expose service_role key in client code
45%
have at least one table with RLS disabled
< 2 min
to exploit exposed Supabase configs

Common Security Issues

Row Level Security Disabled on Production Tables

Frequency: Very CommonStatus: Ongoing pattern
critical

The single most common Supabase security issue is tables deployed to production without Row Level Security enabled. When RLS is disabled, any authenticated user (or anyone with the anon key) can read, write, and delete all data in the table. This is the default state for new tables, and AI code generation tools frequently do not enable RLS.

Technical Details

  • New Supabase tables have RLS disabled by default
  • The anon key + disabled RLS = full public access to the table
  • AI tools often create tables without running ALTER TABLE ... ENABLE ROW LEVEL SECURITY
  • Some developers disable RLS during development and forget to re-enable it
  • Supabase Dashboard shows a warning but many developers miss it

Vulnerable Code

-- DANGEROUS: Table without RLS
CREATE TABLE user_profiles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id),
  full_name TEXT,
  email TEXT,
  phone TEXT,
  address TEXT
);
-- RLS is NOT enabled - anyone can read all profiles

Fixed Code

-- FIXED: Table with proper RLS
ALTER TABLE user_profiles ENABLE ROW LEVEL SECURITY;

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

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

Impact

  • Complete exposure of all table data to any authenticated user
  • Unauthenticated access via the public anon key
  • Mass data exfiltration of user personal information
  • Data modification or deletion by unauthorized users
  • Privacy regulation violations (GDPR, CCPA)

Service Role Key Exposed in Client-Side Code

Frequency: CommonStatus: Ongoing pattern
critical

The Supabase service_role key is a master key that bypasses all Row Level Security. When exposed in client-side code, browser bundles, or public repositories, it gives anyone full admin access to the entire database. AI code generators frequently use the service_role key instead of the anon key because it avoids RLS restrictions and makes development easier.

Technical Details

  • Service role key visible in JavaScript bundles in the browser
  • AI tools use service role key to avoid RLS configuration complexity
  • Key often hardcoded in Supabase client initialization
  • Some generated code uses service role for both client and server operations
  • Exposed keys persist in git history even after removal from code

Vulnerable Code

// DANGEROUS: Service role key in client code
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  'https://xxxx.supabase.co',
  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // This is a service_role key!
  // Bypasses ALL RLS policies
)

Fixed Code

// FIXED: Use anon key in client, service role only server-side
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! // Safe for client use
)

// Server-side only (API routes, server components):
const supabaseAdmin = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY! // Never in client code
)

Impact

  • Complete database access bypass of all RLS policies
  • Read, write, delete any data in any table
  • Access to auth.users table (emails, metadata)
  • Storage bucket access and manipulation
  • Ability to create admin users or escalate privileges

Overly Permissive RLS Policies

Frequency: Very CommonStatus: Ongoing pattern
high

Even when RLS is enabled, many applications have policies that are too permissive. The most common pattern is using 'true' as the USING clause, which allows all authenticated users to access all rows. AI tools frequently generate these permissive policies because they do not understand the application's authorization model.

Technical Details

  • Policies using USING (true) allow all authenticated users to see all data
  • Missing user_id scoping means users can access other users' data
  • SELECT policies without auth checks expose data to anon users
  • INSERT policies without WITH CHECK allow data injection
  • UPDATE/DELETE policies without proper USING allow modifying others' data

Vulnerable Code

-- DANGEROUS: Overly permissive policies
CREATE POLICY "Anyone can read" ON messages
  FOR SELECT USING (true);  -- All users see all messages

CREATE POLICY "Anyone can insert" ON messages
  FOR INSERT WITH CHECK (true);  -- Anyone can create messages as any user

CREATE POLICY "Anyone can update" ON messages
  FOR UPDATE USING (true);  -- Anyone can edit anyone's messages

Fixed Code

-- FIXED: Properly scoped policies
CREATE POLICY "Users can read own messages" ON messages
  FOR SELECT TO authenticated
  USING ((select auth.uid()) = sender_id OR (select auth.uid()) = recipient_id);

CREATE POLICY "Users can insert own messages" ON messages
  FOR INSERT TO authenticated
  WITH CHECK ((select auth.uid()) = sender_id);

CREATE POLICY "Users can update own messages" ON messages
  FOR UPDATE TO authenticated
  USING ((select auth.uid()) = sender_id);

Impact

  • Horizontal privilege escalation between users
  • Users can read other users' private data
  • Data tampering by unauthorized users
  • Privacy violations and trust erosion

Public Storage Buckets with Sensitive Files

Frequency: CommonStatus: Ongoing pattern
high

Supabase Storage buckets can be configured as public or private. AI-generated code frequently creates public buckets for convenience, even when storing sensitive files like user uploads, documents, or profile photos. Public buckets allow anyone with the file URL to access the content without authentication.

Technical Details

  • Buckets created with public: true expose all files via direct URL
  • File URLs are predictable (bucket name + file path)
  • No authentication required to access files in public buckets
  • AI tools often set buckets to public to avoid RLS on storage
  • Uploaded user content (photos, documents) accessible to anyone

Vulnerable Code

// DANGEROUS: Public bucket for user documents
const { data, error } = await supabase.storage
  .createBucket('user-documents', {
    public: true,  // Anyone can access!
    fileSizeLimit: 10485760,
  })

Fixed Code

// FIXED: Private bucket with RLS policies
const { data, error } = await supabase.storage
  .createBucket('user-documents', {
    public: false,  // Requires auth to access
    fileSizeLimit: 10485760,
  })

-- Storage RLS policy (in SQL)
CREATE POLICY "Users can access own files" ON storage.objects
  FOR SELECT TO authenticated
  USING (bucket_id = 'user-documents' AND (select auth.uid())::text = (storage.foldername(name))[1]);

Impact

  • User-uploaded files accessible to anyone
  • Privacy violations for personal documents and photos
  • Potential exposure of sensitive business documents
  • Compliance violations for regulated industries

Realtime Subscriptions Leaking Data

Frequency: OccasionalStatus: Ongoing pattern
medium

Supabase Realtime allows subscribing to database changes via websockets. When RLS policies are not properly configured for Realtime, subscribers can receive change events for data they should not have access to. This is a subtle issue because the data appears correctly restricted in normal queries but leaks through the Realtime channel.

Technical Details

  • Realtime subscriptions respect RLS but policies must cover SELECT
  • Missing SELECT policies may allow Realtime to broadcast all changes
  • Channel-level filtering does not replace RLS
  • Broadcast and Presence features may leak user activity data
  • AI tools rarely configure Realtime security properly

Vulnerable Code

// DANGEROUS: Subscribing to all changes without RLS check
const channel = supabase
  .channel('orders')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'orders'  // Receives ALL orders, not just user's
  }, (payload) => {
    console.log(payload)
  })
  .subscribe()

Fixed Code

// FIXED: Ensure RLS policy + filter for user's data
const channel = supabase
  .channel('my-orders')
  .on('postgres_changes', {
    event: '*',
    schema: 'public',
    table: 'orders',
    filter: `user_id=eq.${userId}`  // Client-side filter
  }, (payload) => {
    console.log(payload)
  })
  .subscribe()

-- RLS policy ensures server-side enforcement
-- CREATE POLICY "Users see own orders" ON orders
--   FOR SELECT TO authenticated
--   USING ((select auth.uid()) = user_id);

Impact

  • Real-time leakage of other users' data changes
  • Exposure of order amounts, messages, or activity
  • Difficult to detect since normal queries may work correctly
  • Privacy violations from leaked real-time events

Known RLS Bypass Techniques

Even with RLS enabled, there are several techniques that attackers use to bypass or circumvent Row Level Security policies. Understanding these helps you build more robust security.

Direct PostgREST Access

Using the Supabase REST API directly with the anon key to query tables without RLS

Risk: High - bypasses frontend restrictions entirely

Manipulating RPC Calls

Calling database functions that use SECURITY DEFINER, which execute as the function owner and bypass RLS

Risk: High - functions may access data the caller should not see

Storage Path Enumeration

Guessing file paths in public storage buckets to access other users' files

Risk: Medium - predictable paths make enumeration easy

JWT Manipulation

Modifying JWT claims when custom claims are used for RLS without proper verification

Risk: Medium - depends on JWT secret exposure

Foreign Key Traversal

Accessing restricted data through related tables that have more permissive policies

Risk: Medium - complex policies may miss indirect access paths

Supabase Security Checklist

Database Security

  • RLS enabled on ALL tables (no exceptions)
  • Each table has SELECT, INSERT, UPDATE, DELETE policies
  • Policies use (select auth.uid()) for user scoping
  • No policies with USING (true) on sensitive tables
  • SECURITY DEFINER functions audited for data leaks
  • Foreign key relationships do not create indirect access

Key Management

  • Only anon key used in client-side code
  • Service role key stored in server-side environment variables
  • No keys hardcoded in source files
  • Keys not committed to git history
  • Service role key rotated if ever exposed

Storage Security

  • Sensitive buckets set to private
  • Storage RLS policies configured per bucket
  • File paths include user ID for scoping
  • No sensitive files in public buckets
  • Upload file type and size limits configured

Authentication

  • Email confirmation enabled
  • Password strength requirements configured
  • OAuth redirect URLs restricted to your domain
  • Rate limiting on auth endpoints
  • Session management properly configured

Check Your Supabase App's Security

VAS detects exposed Supabase keys, missing RLS, permissive policies, and storage misconfigurations automatically. Get a comprehensive security report in minutes.

Frequently Asked Questions

Is the Supabase anon key a security risk?

The Supabase anon key is designed to be public and is not a security risk by itself. It is equivalent to a public API key and is meant to be used in client-side code. Security in Supabase is enforced through Row Level Security (RLS) policies, not by hiding the anon key. However, if RLS is not properly configured, the anon key provides unrestricted access to your database tables. The service_role key, on the other hand, should NEVER be exposed in client-side code as it bypasses all RLS policies.

How do I check if my Supabase RLS is properly configured?

To check your RLS configuration: 1) Go to your Supabase Dashboard > Database > Tables, 2) Verify that RLS is enabled on every table (the lock icon should be active), 3) Review each table's policies to ensure they properly scope data to the authenticated user using auth.uid(), 4) Test by querying data with the anon key - you should only see data that matches your RLS policies, 5) Use VAS to scan your deployed application for RLS issues automatically.

What happens if my Supabase service_role key is exposed?

If your Supabase service_role key is exposed, an attacker has full, unrestricted access to your entire database. The service_role key bypasses all Row Level Security policies, meaning the attacker can read, write, update, and delete any data in any table. They can also access storage buckets, manage users, and perform admin operations. If this key is exposed, you must: 1) Immediately rotate the key in Supabase Dashboard > Settings > API, 2) Update all server-side code with the new key, 3) Audit your database for unauthorized changes, 4) Check if any user data was accessed or modified.

Why do vibe-coded apps have so many Supabase security issues?

AI code generation tools frequently create Supabase configurations with security issues for several reasons: 1) They often disable RLS during development for convenience and never re-enable it, 2) They may use the service_role key in client-side code instead of the anon key, 3) Generated RLS policies are often too permissive (using 'true' for all operations), 4) They do not understand the application's authorization requirements, so they cannot write proper user-scoped policies, 5) They prioritize getting the app working over implementing proper security controls.

Has Supabase itself been breached?

Supabase as a platform has not had a major public security breach. The incidents documented on this page are not vulnerabilities in Supabase's infrastructure - they are misconfigurations in applications built on Supabase. Supabase's security model is sound when properly configured: RLS policies enforce data access at the database level, the anon key is designed for public use, and the platform provides robust authentication. The issue is that many developers, especially those using AI coding tools, do not configure these features correctly.

Last updated: February 2026