Anyone could read every user's email

In this controlled demo, the profiles table has row level security switched off, so an anonymous visitor can ask the database for every row and get it.

Technical name: Supabase · row level security offControlled demo · fake data

Can strangers read my Supabase data?

Yes, if row level security (RLS) is off on a table. Supabase exposes your tables through an API that anyone can call with the public anon key, and RLS is the only thing that decides which rows each visitor may see. With RLS off, an anonymous request returns every row.

How to tell if your app has this

No code needed.

  1. In the Supabase dashboard, open Table Editor. Any table with user data that shows an "RLS disabled" or "Unrestricted" label is open.
  2. Open Authentication > Policies. Every table with personal data should have RLS enabled and at least one policy.
  3. Ask your AI tool: "List every table in my Supabase project, whether RLS is enabled, and its policies."

Before and after

Before
Anonymous visitor asksRLS: OFF
select id, email from profiles
200 OK · every email
anna@example.test
marc@example.test
lina@example.test

Anonymous request, every email returned.

After
Anonymous visitor asksRLS: ON
select id, email from profiles
200 OK · 0 rows
No error. Just nothing to see.

Same request, zero rows. No error, just nothing to see.

Why AI-built apps end up with this

AI tools create tables so the feature works, and they often do it with SQL. Tables created with plain SQL start with RLS turned off unless something turns it on. The app looks finished because reading and writing works, and nothing on screen shows that strangers can read the same table.

Fix it yourself

How do I fix it? Paste this into your AI tool

In my Supabase project, the profiles table has row level security (RLS) turned off. Turn RLS on for every table that holds user data, and add policies so a signed-in user can only read and update their own row (auth.uid() = user_id). Never put the service_role key in the frontend. Then show me how to check that an anonymous request to profiles returns no rows.
Works with Lovable, Bolt, Cursor, Replit and similar tools. Then run the check-up below on your own app.

What the demo shows

Same request before and after the fix
Same requestBeforeAfter
Anonymous request to profiles200 OK, every row200 OK, 0 rows

Shown in the demo video. Test these on your own app before you rely on them.

Common mistakes when fixing this

  • The public anon key in your frontend is not the leak. It is meant to be public; the rules behind it are what matter.
  • RLS often answers with an empty list instead of an error. That is expected.

Questions people ask

Is my Supabase anon key a secret I leaked?

No. The anon key is meant to be public and ships in every Supabase frontend. What protects your data is row level security and its policies. The service_role key is the one that must never be in the frontend.

I turned RLS on and now my app shows nothing. Is it broken?

RLS with no policies blocks every row, so empty results are expected. Add a policy that allows each signed-in user to read their own rows, for example using auth.uid() = user_id.

Why do I get an empty list instead of an error?

When RLS hides rows, Supabase usually answers with an empty result, not a 401 or 403. That is normal. Test with an anonymous request and a second user to confirm each one only sees what they should.

For developers: the cause and the fix in code
− -- profiles: row level security OFF
+ alter table profiles enable row level security;
+ create policy "read own row" on profiles
+   for select using (auth.uid() = user_id);

Simplified. Your stack will look different; the principle is the same.

Learn more