← Back to blog
SUPABASE

Supabase RLS: The One Setting That Exposes All Your User Data

If you're using Supabase and you haven't explicitly enabled Row Level Security (RLS) on every table, your database is publicly readable. Not "maybe." Not "in theory." Right now, anyone can query your entire database using just your project URL and anon key -- both of which are in your client-side JavaScript.

This is the single most common critical vulnerability we find at vibeAudit. Over 60% of the Supabase apps we scan have at least one table with RLS disabled. Let's fix that.

RLS in 30 Seconds

Row Level Security is a PostgreSQL feature that Supabase exposes through its dashboard. When RLS is enabled on a table, every query must pass through a policy that you define. If there's no matching policy, the query returns zero rows. If RLS is disabled, every query returns every row.

Think of it this way:

  • RLS OFF = the table is a public spreadsheet. Anyone with the URL can read everything.
  • RLS ON, no policies = the table is locked. Nobody can read anything (including your app).
  • RLS ON, with policies = the table is properly secured. Each user sees only what you allow.

Your Anon Key Is an Unlocked Door

Your Supabase anon key isn't a secret. It's embedded in your client-side JavaScript and visible to anyone who opens browser DevTools. Supabase designed it this way intentionally -- the anon key is supposed to be safe because RLS restricts what it can access.

But when RLS is disabled, the anon key becomes an unrestricted access pass. Here's what an attacker can do:

# An attacker only needs your project URL and anon key
# (both visible in your JavaScript bundle)

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

# If RLS is disabled, this returns EVERY row in the users table:
# emails, names, profile data, private settings -- everything

This isn't hypothetical. We see this in production apps every day.

How to Enable RLS

Step 1: Enable RLS on every table

Go to your Supabase SQL Editor and run:

-- Find all tables that don't have RLS enabled
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';

-- Enable RLS on each table
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.orders ENABLE ROW LEVEL SECURITY;
-- Repeat for EVERY table in your public schema

Step 2: Add read policies

After enabling RLS, your app will break because no one can read anything. You need to add policies that define who can access what.

-- Users can read their own profile
CREATE POLICY "Users can read own profile"
  ON public.profiles
  FOR SELECT
  USING (auth.uid() = id);

-- Users can update their own profile
CREATE POLICY "Users can update own profile"
  ON public.profiles
  FOR UPDATE
  USING (auth.uid() = id);

-- Public posts are readable by everyone (if you want that)
CREATE POLICY "Public posts are readable"
  ON public.posts
  FOR SELECT
  USING (is_published = true);

-- Users can only manage their own posts
CREATE POLICY "Users manage own posts"
  ON public.posts
  FOR ALL
  USING (auth.uid() = author_id);

Step 3: Add insert and delete policies

-- Users can only insert rows with their own user_id
CREATE POLICY "Users insert own data"
  ON public.messages
  FOR INSERT
  WITH CHECK (auth.uid() = sender_id);

-- Users can only delete their own messages
CREATE POLICY "Users delete own messages"
  ON public.messages
  FOR DELETE
  USING (auth.uid() = sender_id);

Common RLS Mistakes

Even after enabling RLS, there are several patterns that create false security:

Mistake 1: Overly permissive policy

-- BAD: This allows anyone to read everything (same as no RLS)
CREATE POLICY "Allow all" ON public.profiles
  FOR SELECT USING (true);

-- GOOD: Restrict to the authenticated user
CREATE POLICY "Read own data" ON public.profiles
  FOR SELECT USING (auth.uid() = user_id);

Mistake 2: Forgetting the service_role bypass

-- The service_role key BYPASSES RLS entirely.
-- NEVER use it in client-side code.
-- Only use it in server-side functions (Edge Functions, API routes).

-- If your Supabase client is initialized with the service_role key
-- in client code, RLS gives you zero protection.

Mistake 3: Not covering all operations

-- If you only add a SELECT policy, users can still INSERT,
-- UPDATE, and DELETE without restriction (if RLS is enabled
-- but only SELECT has a policy, other operations are denied
-- by default -- which is safe, but might break your app).

-- Best practice: explicitly define policies for each operation
-- SELECT, INSERT, UPDATE, DELETE

Enabled RLS but not sure it's configured right? vibeAudit tests your policies too — paste your URL, 30 seconds, free.

How to Test If Your RLS Is Working

After setting up your policies, test them manually:

# Test with the anon key (unauthenticated)
curl 'https://YOUR_PROJECT.supabase.co/rest/v1/profiles?select=*' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

# Expected: empty array [] or error (not a list of all users)

# Test with a user's JWT token (authenticated as User A)
curl 'https://YOUR_PROJECT.supabase.co/rest/v1/profiles?select=*' \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer USER_A_JWT_TOKEN"

# Expected: only User A's profile, not other users' data

If unauthenticated requests return user data, your RLS isn't configured correctly.

Stop Checking Manually

Nobody remembers to test RLS after every schema change. vibeAudit automates this: our Supabase-specific scanner detects your project URL, checks every table for RLS status, and tests whether unauthenticated requests can access data they shouldn't. Also see our Lovable security guide and Firebase equivalent.

FAQ

Q: Is Supabase RLS enabled by default?

No. You must enable it manually on each table with ALTER TABLE your_table ENABLE ROW LEVEL SECURITY.

Q: Can anyone read my Supabase database?

If RLS is disabled, yes. Anyone with your anon key (which is in your JavaScript bundle) can query every row.

Q: Does the service_role key bypass RLS?

Yes. Never use the service_role key in client-side code.

Added a new table? Scan it. Changed a policy? Scan it. vibeAudit catches open tables in seconds. Free. Read-only scan, your app is never modified.

Frequently asked questions

What is Supabase Row Level Security (RLS)?

RLS is a Postgres feature that filters rows at the database level based on the authenticated user. When RLS is off, anyone with the anon key (visible in your JS bundle) can read every row in the table. vibeAudit's April 2026 research found 100% of 46 AI-built apps had at least one RLS misconfiguration.

How do I check if my Supabase table has RLS enabled?

Paste your URL into vibeAudit — it runs live RLS bypass probes against every public table using the same anon key your app ships in its client bundle. If a table returns rows without an auth token, RLS is off or the policy is USING(true).

Does enabling RLS without policies secure my table?

Yes. 'RLS enabled with no policies' means deny-all: every query returns zero rows unless a policy explicitly grants access. That is the safe starting state. Add policies per-operation (SELECT, INSERT, UPDATE, DELETE) from there.

What is the USING(true) policy mistake?

USING(true) is a policy that allows every row to be read by every user — it passes Supabase's 'RLS enabled' check but provides zero row-level filtering. vibeAudit flags USING(true) policies as CRITICAL because they're functionally identical to RLS off.

How do I write a correct RLS policy?

For a 'users can only read their own rows' policy on a table with a user_id column: CREATE POLICY "own rows" ON your_table FOR SELECT USING (auth.uid() = user_id);. vibeAudit's paid deep scan returns copy-paste SQL for every RLS gap it finds.

Why do AI code generators like Lovable and Cursor miss RLS?

RLS misconfiguration produces no visible symptom during development — the app still renders, buttons still work, data still shows up. AI generators optimize for 'does it render', not 'is it safe'. Lovable apps in vibeAudit's 46-app audit averaged 8 critical findings each, with RLS gaps being the most common issue.

Is vibeAudit safe to run on a production Supabase app?

Yes. vibeAudit runs read-only probes — it never writes, modifies, or deletes data. It uses the same public anon key your app already ships in its client bundle, so it tests exactly what an attacker with a browser would see.

Is your app vulnerable?

Scan your app for free — read-only, no signup, ~30 seconds. vibeAudit runs the tests this post describes.

Scan your app free