Supabase anon key exposed: what it allows and how to test RLS
The key is meant to be public. What it can read is up to your database rules. Test every table as the anon role and mark each result.
Published Last updated
The anon key, now also called the publishable key, is meant to be public. Anyone can pull it out of your app, so it can reach whatever your database rules allow and nothing more. If a table has row level security off, or a policy that lets everyone in, that key reads it. Test each table as the anon role.
Which Supabase keys are meant to be public?
Supabase's API keys page documents two pairs. The legacy pair is the anon and service_role keys. The newer pair is the publishable key (starts with sb_publishable_) and the secret key (starts with sb_secret_). In each pair, the first is for clients and the second is for servers. We checked the docs on October 24, 2026.
| Key | Ships in the app? | Database role | What limits it |
|---|---|---|---|
| anon / publishable | Yes, by design | anon, or authenticated once a user signs in | Grants and RLS policies |
| service_role / secret | Never | service_role | Nothing: it bypasses every RLS policy |
| Your project URL | Yes | Not a key | Public, so assume attackers know it |
Both kinds of key can be read by anyone who downloads the app. Expo says it plainly: variables that start with EXPO_PUBLIC_ are visible in plain text in your compiled application. That is why the anon key can ship, and why a secret key cannot. If Newly built your app, you can download the code or connect your own repository and search it for every key.
What decides whether the anon key can read a table?
The Data API is built on PostgREST. It reads the role in your token and switches to the database role with that name for the request. It authenticates, and the database authorizes. Two layers then apply, and Supabase's Data API security page names both: grants decide which roles can reach a table, view or function, and RLS policies decide which rows those roles can read or change.
Two sentences from the docs matter most. On a table without RLS, the page says tables and views exposed through the Data API can be accessed by any role with matching grants
. And the same page lists the default grants on new objects in the public schema for anon, authenticated and service_role, with Supabase stating it is changing the platform default so that exposure becomes opt-in. As of October 24, 2026 the docs describe that as a change under way, so check your own project's Data API settings instead of assuming.
The other half is the RLS guide: with RLS enabled and no policies, no data is accessible through the API with a publishable key until you create policies.
How do I test each table as anon?
Checking that RLS is switched on is half of it. A policy can be on and still wrong, so read the tables the way the public key would. Use only your own project.
1. Read every table as anon (5 minutes)
In the Supabase SQL editor, switch role inside a transaction and roll it back, as the RLS guide shows with set local role. Repeat for every table in every schema the Data API exposes.
begin;
set local role anon;
select count(*) from public.your_table;
rollback;A table with no rows tells you nothing, because zero is the answer either way. Put one test row in it first, or test a table that already has data.
2. Try the writes as anon (3 minutes)
In the same rolled back transaction, attempt an insert, an update and a delete on each table that holds user data. All three should fail. A write that succeeds leaves the table open to anyone with the app.
3. Read as a second user (5 minutes)
Anon is the stranger. The more common mistake is one signed-in user reading another's rows, which OWASP calls broken object level authorization. Create two test accounts, put a row in the first, and request it as the second. The RLS guide shows how to set the user identity in SQL.
4. Open the advisor (2 minutes)
Supabase's database advisors include lint 0013, which says anyone with your project URL can read, edit and delete all data in a table that has RLS off, and lint 0007, where a policy exists but RLS is off, so the policy does nothing. A clean advisor is a hint, not proof, so keep the anon output too.
What does each result mean?
| Result as anon | What it tells you | State | Next step |
|---|---|---|---|
| Rows come back, table holds user data | RLS is off, or a policy lets anon in | Detected | Enable RLS, replace the policy, retest, then ask what was read |
| Permission denied error | Grants block anon from this table | Confirmed by owner evidence for this table and this action | Keep the output and the date |
| Zero rows, table has rows | RLS is on and no policy matches anon | Confirmed by owner evidence for reads | Now run the write tests and the second user test |
| Zero rows, table is empty | Nothing | Unknown | Add a test row and rerun |
| Only a view or function was tested | Tables behind it may still answer a different path | Unknown | Test the underlying tables and the view separately |
Which policies still let everyone in?
Turning RLS on is not the finish line. Read each policy for these patterns:
- A condition of
truefor anon. The RLS guide says a policy that readsto anon using ( true )gives every unauthenticated visitor read access to every row the role can reach. Fine for data meant to be public, wrong for the rest. - A policy that checks the user is signed in but not which rows are theirs. Every signed-in user then sees every row.
- A view over a protected table. The docs say that on Postgres 15 and above a view obeys the underlying policies when
security_invoker = true, and the advisor flags views that bypass RLS (lint 0010) and views that expose user data (lint 0002). - A table created later, by you or by an AI agent, in the same exposed schema. UpGuard reported that tables created programmatically through the API do not have RLS enabled by default, so repeat the test after every schema change.
How common is this?
In a study published October 14, 2026, UpGuard gathered about 300,000 domains with signs of Supabase use and identified 16,326 databases exposing readable tables, over half with signs of personal data. The causes it lists are missing RLS, policies that fail to restrict access, and public keys misused as credentials. In February 2026 The Register reported a researcher's claim that an AI-built app exposed about 18,700 user records, with the builder saying acting on its recommendations is up to the user. Both are researcher-reported figures, not audited counts.
What if a secret or service_role key was in my app?
Treat it as public, because it was. A secret key skips RLS, so RLS tests will not show the damage. GitHub's guidance for any leaked credential is that removing it from the code is not sufficient and the key must be revoked with its provider. Supabase's key guidance lists the order: create a new key, replace the old one everywhere, confirm everything uses the new one, then delete the old key. Fix the cause of the leak first, or the new key leaks the same way.
Then move the work that needs the key to a server you control, such as an edge function, and let the app call it with the signed-in user. The general runbook is in Red's guide to rotating secrets after a breach. If your logs show bots asking for /.env, the 30-second check tells you whether the file is actually served.
This test is one of six in the 15-minute check for AI-built apps. For the AI provider keys in your app, see rotating LLM provider keys, and OWASP's Mobile Top 10 lists improper credential usage as its first category. All pages sit under Security.
Where do you stand?
Every item starts as unknown. Move it to confirmed only when you hold the evidence listed under it, with the date you ran the test.
Your Supabase access check
- Detected: You found the problem.
- Unknown: Not checked yet, or the check could not tell.
- Confirmed by owner evidence: You checked and kept the proof.
Your answers stay in this browser tab. Nothing is saved or sent.
Frequently asked questions
Is it a problem that my Supabase anon key is in my app?
No, that is where it belongs. Supabase documents the anon and publishable keys as fine to ship in web pages and mobile apps, because anyone who has the key can only reach what your row level security policies and grants allow. The problem is a table where those rules are off or let everyone through.
I turned RLS on and now my app shows no data. What happened?
With RLS enabled and no policies, the API returns no rows for the public key. That is the intended starting point. Add a policy for each action a signed-in user needs, scoped to their own rows, and test again as anon and as a second test user.
Does a scanner finding no exposed table mean my database is fine?
No. An outside check reports what it could read at the time it ran. It cannot see a table in a schema it did not reach, a policy that only fails for one user, or a key it never found. Treat a clean outside result as unknown until you have run the anon test on every table yourself and kept the output.
What is the difference between the anon key and the service_role key?
The anon key maps to the anon database role, which RLS restricts. The service_role key maps to a role that bypasses every RLS policy, so it is for servers only. Supabase's newer publishable and secret keys follow the same split. If a service_role or secret key shipped in an app, rotate it.
Does the red.run.cloud scan test my RLS policies?
The free scan looks for API keys and tokens in shipped bundles and for databases and services reachable from the internet, matched against known issues. It cannot read your policies. The anon test on this page is the way to check them, and the $100 full scan can then look for a path an outsider could use.
Do I need to do this if an AI builder or agent created my tables?
Yes. UpGuard noted in October 2026 that tables created through the API, which is how coding agents talk to Supabase, do not have RLS enabled by default. Whoever or whatever created the tables, the settings are in your project and the check is yours to run.
Sources
All pages opened and checked on October 24, 2026.
- API keys, Supabase docs
- Using API keys, including rotation, Supabase docs
- Row Level Security, Supabase docs
- Securing your Data API, Supabase docs
- Database advisors (lints 0002, 0007, 0010, 0013), Supabase docs
- Everything, everywhere: systemic data exposure in Supabase apps, UpGuard, October 14, 2026
- AI-built app exposed 18K users, researcher claims, The Register, February 27, 2026
- Authentication and roles, PostgREST docs
- API1:2023 Broken Object Level Authorization, OWASP API Security Top 10
- OWASP Mobile Top 10, OWASP Foundation
- Environment variables in Expo, Expo docs
- Remediating a leaked secret, GitHub docs
