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.
Demo built and tested 17 Sept 2026 · Published 23 Sept 2026 · Last reviewed 23 Sept 2026 · By Ctrl+Z Therapy (how we test)
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.
- In the Supabase dashboard, open Table Editor. Any table with user data that shows an "RLS disabled" or "Unrestricted" label is open.
- Open Authentication > Policies. Every table with personal data should have RLS enabled and at least one policy.
- Ask your AI tool: "List every table in my Supabase project, whether RLS is enabled, and its policies."
Before and after
Anonymous request, every email returned.
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.
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 | After |
|---|---|---|
| Anonymous request to profiles | 200 OK, every row | 200 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.