Is your Supabase anon key safe in the frontend? Yes. Here's what isn't.
If you've built an app on Supabase with Lovable, Bolt, v0 or Cursor, you've probably opened DevTools, seen a long eyJ... key in your JavaScript, and felt your stomach drop. Good news first: that key is supposed to be there. Here's what it does, and the four things that do leak data in Supabase apps.
The anon key is public by design
| Key | Starts with | Where it belongs |
|---|---|---|
| anon / publishable | eyJ... (role anon) or sb_publishable_ | The browser. Fine to ship. |
| service_role / secret | eyJ... (role service_role) or sb_secret_ | Only on a server. Never in the browser. |
The anon key only identifies your project. What someone can do with it is decided entirely by your Row Level Security (RLS) policies. So the anon key is safe only if RLS is doing its job.
1. Tables with RLS turned off
With RLS off, anyone who copies your anon key can read, and often write, the whole table with one request. Check every table in the SQL editor:
select tablename, rowsecurity
from pg_tables
where schemaname = 'public';
Anything showing false is open. Turn RLS on and add a policy that ties rows to their owner:
alter table public.profiles enable row level security;
create policy "Users read their own profile"
on public.profiles for select
using (auth.uid() = user_id);
Watch out for policies with using (true). They switch RLS on but let everyone through.
2. The service_role key in the browser
The service_role (or sb_secret_) key bypasses RLS completely. AI tools sometimes put it in a VITE_ or NEXT_PUBLIC_ variable to make an admin feature work, and anything with those prefixes is shipped to every visitor. If you find it, rotate it in Supabase (Settings → API) and move the admin work into an Edge Function.
3. Storage buckets
Storage has its own policies on storage.objects, separate from your tables. A public bucket means anyone with a file's URL can download it.
4. Views and functions that skip RLS
- Views run with their creator's rights by default, so a view over a protected table can expose it. On Postgres 15+, create them
with (security_invoker = on). - Functions marked SECURITY DEFINER also run with their owner's rights. If one is callable through the API, make sure it only returns what the caller should see.
The 2-minute check
- In DevTools → Sources on your live app, search for
service_role,sb_secret_,sk-andsk_live. You should find none. - Run the SQL above. Every table should show
true. - Look for
using (true)in your policies. - Check which storage buckets are public.
LeakPatrol checks the outside of your app for leaked keys for free, and once you prove you own the site, it also tests which Supabase tables can be read without logging in.
Check your app in a minute
Free, read-only, no sign-up. Every problem comes with a plain-English fix and a prompt for your AI tool.
Scan your app freeQuestions
Is the Supabase anon key safe to expose?
Yes. The anon (or publishable) key is designed to be public. Your data is protected by Row Level Security policies, so it is only safe if RLS is enabled with correct policies on every table.
What happens if Row Level Security is off in Supabase?
Anyone with your anon key, which is visible in your frontend, can read and often modify every row in that table without logging in.
Can the service_role key be used in the browser?
No. The service_role or sb_secret_ key bypasses all Row Level Security. Keep it on a server or in an Edge Function and rotate it if it was ever exposed.
How do I check if RLS is enabled on all my Supabase tables?
Run select tablename, rowsecurity from pg_tables where schemaname = 'public'; in the SQL editor. Any table showing false has RLS off.