TL;DR: The Supabase anon key is meant to be public. It sits in your browser code on purpose, and Row Level Security (RLS) is what keeps it safe. The service_role key is the opposite. It skips every RLS policy you wrote, so it must never leave your server. Get those two mixed up and a stranger can read your whole database.

Did you build your app with Lovable, Bolt, Cursor, or Replit? Did you wire up Supabase for the backend? Then two keys are doing very different jobs in your project. Most founders never learn which is which until something leaks.

The supabase anon key vs service role key mix-up is one of the most common critical findings we see. This guide sorts it out in plain terms. You will see what each key does, where it belongs, and how to check your app in about a minute.

Want the fast answer for your app? Run the free scan on your live URL first. No code access. No install. Then read on.

QuestionAnswer
Is the anon key safe to expose?
Yes, by design. But only if RLS is on for every table.
Is the service_role key safe to expose?
Never. It bypasses RLS and gives full read and write access to your data.
What actually protects the data?
RLS policies. The anon key is safe only because RLS stands behind it.
What are the new keys?
Publishable keys replace anon. Secret keys replace service_role. Same rules, better guardrails.

What the Supabase Anon Key Is, and Why It's Public by Design

The anon key is the public API key Supabase makes for every project. It sits in your .env file. It also ships inside your browser code, where anyone can read it with dev tools.

That is not a leak. That is the plan.

Supabase built the anon key to be seen. It names your project to the API layer and carries the anon role. Postgres then checks that role against your RLS policies. Only then does it return a row. Supabase's own docs put it plainly: the anon key is "safe to expose," and full protection means "you have enabled Row Level Security on all tables."

So the anon key being public is not the risk. The risk is what happens when no RLS policy stands behind it. Then the same public key can read the whole table.

Scanner rule — Supabase detected

"Supabase detected: verify row-level security policies on all tables. The anon key is public by design — RLS is your only access control layer." That line is the exact note our scanner returns for a Supabase app. You can find it in platformFingerprintScanner.js:230. It fires on every Supabase app we scan.

What the Service Role Key Is, and Why It Should Never Touch a Browser

The service_role key is a different animal. It carries admin rights and skips RLS on purpose. That lets your backend run migrations, batch jobs, and admin tasks. No fighting your own policies.

It belongs in one place. That place is your server environment. Never in browser code. Never in a mobile app binary. Never in a public GitHub repo.

Under the hood, the service_role key maps to a Postgres role. That role carries the BYPASSRLS attribute. It skips any and all RLS policies you attach. Supabase's docs say it outright: this role "has full access to your project's data."

If the service_role key lands in your frontend, RLS stops mattering. It is the master key. Every policy you wrote turns useless. The key is now in someone else's hands.

Here is the part that bites vibe-coded apps. AI code generators do not reliably tell the two keys apart. A query fails because RLS blocked it. The quick "fix" an assistant often suggests? Swap in the service_role key. That clears the error. It also hands the browser a master key.

Supabase Anon Key vs Service Role Key: The Core Differences

Here is the side-by-side founders actually need.

AttributeAnon keyService role key
Meant to be public?YesNo, never
Respects RLS policies?Yes, alwaysNo, bypasses RLS
Where it belongsBrowser, mobile app, client codeServer environment only
Risk if RLS is offWhole table readable via the public APIWhole database, no exceptions
Common vibe-coding mistakeShip with no owner-scoped policyPaste into the frontend "to make it work"
New key namesb_publishable_...sb_secret_...

See the pattern. The question is not which key is more dangerous. It is whether RLS is doing its job on the anon side. And whether the service_role key ever left the server.

The New Supabase Keys: Publishable and Secret

Supabase is changing how keys work, and the new names make the rule easier to remember. During the switch, you can run both the old and new keys at once.

The new secret key adds two real guardrails. First, it rotates on its own. You can swap a leaked key in seconds, with no broken user sessions. The old service_role key was tied to your JWT secret. Rotating it risked downtime. Second, the API gateway rejects a secret key sent from a browser. It checks the request headers. It returns HTTP 401 if a secret-shaped key shows up client-side.

The mental model does not change. One key is publishable. One key is secret. Legacy keys still work today. But Supabase plans to retire them, so new projects lean on the new format. If you are starting now, use publishable and secret from day one.

Where Vibe-Coded Apps Get This Wrong

Across the first 100 vibe-coded apps we scanned, the average Launch Readiness Score was 42 out of 100. The anon-vs-service_role mix-up showed up again and again. Three failure patterns repeat.

This is the missing 20% again. The AI built the table, wired the login flow, and shipped a working demo. It never asked whether the public anon key could read every row in that table.

Scanner rule — AI platform detected

For apps built on Lovable or Bolt, our scanner adds a second note: "AI-generated apps frequently ship with Supabase anon key exposed and no RLS. Run an authorized Deep Audit to verify." It lives in platformFingerprintScanner.js:236. We wrote that rule because the pattern is that consistent.

Row Level Security Is the Real Line

RLS is not middleware you bolt on later. It is the whole reason the anon key is safe to expose. Read our Supabase RLS security guide before you write another policy. Then ask three questions about every table.

  1. Is RLS enabled on every table, including join tables and lookup tables?
  2. Are policies owner-scoped? They should check auth.uid() against a user_id column, never USING (true).
  3. Do any RPC functions run with admin rights and skip a role check inside the function body?

We point founders to two real cases. The Moltbook breach exposed 47,000 user records because RLS was off. No exotic exploit needed. And CVE-2025-48757 left 170 Lovable projects readable through the public anon key alone. Both come down to the same gap: a public key with no RLS behind it.

Why is AI code so prone to this? Carnegie Mellon's SusVibes benchmark tested AI-written code. It covered 200 real tasks and 77 flaw types. The code was correct 61% of the time. It was secure only 10.5% of the time. The app runs. It rarely holds up.

See which of your tables the anon key can read

Free scan. Live URL. No code access. Results in about 60 seconds.

Run the free scan

How to Check Your Setup in Under Two Minutes

You do not need a security consultant for a first answer. Two tools get you there.

Start with the Supabase RLS checker. Point it at your project URL. It probes common read and write patterns using the anon role. So you see what the public key can actually reach.

For the wider view, our free scan takes about 60 seconds and needs no code access. We check the live URL, not your repo. There is nothing to hand over and nothing to set up. You get a score out of 100 and the gaps to fix.

A Pre-Launch Supabase Key Checklist

  1. RLS is on for every table. Owner-scoped. Never USING (true).
  2. The anon or publishable key is the only key in client code. Confirm it in your build output.
  3. The service_role or secret key lives on the server only. Not in the bundle. Not in the repo.
  4. You grepped your frontend build for service_role and sb_secret_ and found nothing.
  5. RPC functions check the caller's role inside the function, not just at the table.
  6. You moved to publishable and secret keys where you can, for painless rotation.
  7. You ran a scan against the live URL, not just a code review.

Built on Lovable? Our Lovable security audit covers that builder's default gaps. On Bolt? The Bolt security audit does the same.

Why This Is Also a Compliance Problem

A public anon key with no RLS is not just an engineering slip. Once EU user data leaks, the law gets involved. What matters is where your users live, not where your company is. One EU signup, and the rules apply. The exposure becomes a GDPR problem with real money attached.

Add an AI feature and the EU AI Act layers on more duties. Its Article 50 transparency rules are due from 2 August 2026. Our compliance check tests for both and hands back a score with fixes.

Why This Needs More Than a Code Review

An exposed key does not look broken. Your login works. Your dashboard loads. The demo shines. That is the trap.

AI-written code is often correct but rarely secure. It ships the table, the auth flow, and the redirect. It does not ship the threat model. Nobody told the model to plan for a stranger calling your public API with the anon key. So it did not.

This is the same gap we keep finding. Disabled RLS. Keys readable in the browser. Missing rate limits and security headers. It is a batch of skipped choices, not one bad line. A live scan catches it before a user does. See our vibe coding security guide for the full picture. Or our pricing for a CTO-level fix list.

The Fix Is a Checklist, Not a Rewrite

The anon key belongs in your client code. The service_role key never does. And neither one protects you on its own. RLS has to be doing its job on every table.

The functional app is already there. The RLS policies, the key placement, the rotation plan? Those are the missing 20%. That is exactly what a scan surfaces. Run the free scan before someone else finds the gap first.

Frequently Asked Questions

Is it safe to expose the Supabase anon key in frontend code?

Yes. The anon key is built to be public and sits in your client bundle on purpose. It is only safe when RLS policies control what that key can read or write. With RLS off, the anon key can read whole tables.

What happens if the service_role key ends up in a client bundle?

The service_role key bypasses RLS completely. Anyone who pulls it from your bundle gets full access. They can read and write every table. It is one of the most serious findings we see in vibe-coded apps.

Should I switch to the new Supabase publishable and secret keys?

Yes, where you can. The publishable key replaces the anon key and is safe to expose. The secret key replaces the service_role key and is backend only. Secret keys rotate on their own and are rejected if used from a browser.

Can I use the service_role or secret key in a Next.js API route?

Yes, if the code runs server-side only. It must never get bundled into client JavaScript. Check your build output to confirm the key is not leaking into a public chunk.

How do I know if my Supabase RLS is actually working?

Run the free Supabase RLS checker against your project URL. It tests read and write exposure using the anon role directly. A full audit goes further. It checks RPC functions, policy logic, and role scoping, table by table.

How do I check my app for an exposed key?

Run a free scan at launchreadycode.com. It checks your live URL for keys readable in the browser and for missing RLS. No code access. Results in about 60 seconds.