Guides / Supabase

Is my Supabase anon key leaked?

Last reviewed · VibeShield

Short answer: no. Seeing your anon key in the browser does not mean it leaked. The anon key, and its newer replacement the publishable key (sb_publishable_...), is meant to ship in your frontend. Supabase's own key table says the publishable key is "Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code." What protects your data is table grants plus Row Level Security (RLS).

The key you do need to worry about is the secret key (sb_secret_...) or the legacy service_role key. If either one is in your frontend, your bundle or your repo, treat it as leaked and rotate it. The steps are below.

Which Supabase key is in your app?

Supabase has four key types. This summary comes from the API keys docs:

KeyLooks likeMaps to Postgres roleSafe in the browser?
Publishable keysb_publishable_...anon (signed out) or authenticated (signed in)Yes, by design
Legacy anon keylong JWT starting eyJanon / authenticatedYes, same as publishable
Secret keysb_secret_...service_role (bypasses RLS)Never
Legacy service_role keylong JWT starting eyJservice_role (bypasses RLS)Never

Supabase says it is "deprecating the anon and service_role keys by the end of 2026," so new projects should use publishable and secret keys.

How to tell two eyJ... keys apart: both legacy keys are JWTs, so they look alike. The middle part of a JWT holds a role claim, either "role":"anon" or "role":"service_role". Decode it on your own machine only. Never paste a secret key into a website, a chat or an AI tool. Supabase's own rules say a secret key must never travel "over chat, email, or SMS."

Why a public key is fine: grants first, then RLS

The key says what is calling (your web app), not who. Supabase Auth handles who. In Supabase's words: "Postgres evaluates table grants first, and only then applies Row Level Security."

That gives you two gates:

  1. Grants. Does the anon or authenticated role have select, insert, update or delete on the table at all? If not, the request fails with a permission error (code 42501).
  2. RLS policies. If the role has a grant, which rows can it see or change?

The RLS docs spell out what happens if gate 2 is missing: "A table in an exposed schema without RLS is readable and writable by any role with a grant on it." So the real question is never "can people see my key?" It is "which tables can the anon and authenticated roles reach, and do policies limit the rows?"

How to check your own project

1. Run the Security Advisor

In the Supabase dashboard, open Security Advisor. Supabase also exposes the same checks through the CLI (supabase db advisors) and MCP (Advisors docs). Pay attention to:

  • 0013 rls disabled in public: rated ERROR. If RLS is off on a public table, "anyone with the project's URL can CREATE/READ/UPDATE/DELETE (CRUD) rows" (lint 0013).
  • 0008 rls enabled no policy, 0024 permissive rls policy, 0010 security definer view, 0023 sensitive columns exposed and 0025 public bucket allows listing.

Supabase notes "some findings may be intentional," so check each against what you meant to expose.

2. Read your grants and RLS status (SQL Editor, read-only)

These queries only read the system catalog. They don't change anything.

-- Which public tables have RLS on?
select c.relname as table_name, c.relrowsecurity as rls_enabled
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind in ('r', 'p')
order by rls_enabled, table_name;

-- What can anon and authenticated do on each table?
select table_name, grantee,
       string_agg(privilege_type, ', ' order by privilege_type) as privileges
from information_schema.role_table_grants
where table_schema = 'public'
  and grantee in ('anon', 'authenticated')
group by table_name, grantee
order by table_name, grantee;

-- Review policies. Watch for "true" on anon/authenticated.
select tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename, policyname;

Any table that appears in the grants list with rls_enabled = false should be fixed first.

3. Test from the outside, on your own project only

You can confirm what a signed-out visitor sees without downloading any rows. In your own app's code or a local script, using your own publishable key, ask for a count only:

const { count, error } = await supabase
  .from('your_table')
  .select('*', { count: 'exact', head: true })

head: true returns the count without the data (supabase-js select reference). If a private table returns a count above zero while you are signed out, signed-out visitors can read it. An error mentioning 42501 usually means anon has no grant on that table. It can also come from column-level restrictions or a schema the API doesn't expose.

An empty result does not prove you're fine. It says nothing about signed-in users or about writes. Create two test accounts and check that account A can't read or change account B's rows.

What to fix (safe patterns)

Enable RLS and add owner-scoped policies, one table at a time. This pattern follows the RLS docs:

alter table public.your_table enable row level security;

create policy "Users can view their own rows"
  on public.your_table for select
  to authenticated
  using ((select auth.uid()) = user_id);

create policy "Users can create their own rows"
  on public.your_table for insert
  to authenticated
  with check ((select auth.uid()) = user_id);

Trim grants to what each role needs. The RLS docs warn: "Adding policies doesn't take those grants back. A table protected only by policies still hands anon an insert path if you never revoke the grant." If signed-out visitors should never touch a table:

revoke all on table public.your_table from anon;

Be careful with using (true). Supabase says a policy that reads to anon using (true) "grants every unauthenticated visitor read access to every row the role can already reach through grants. Use it only for data that is meant to be public."

Fix views. "Views bypass RLS by default." On Postgres 15+, create views with (security_invoker = true) so they obey the underlying table's policies. On older versions, revoke anon/authenticated access or move the view to a schema the API doesn't expose.

Don't "fix" errors by switching keys. The secret key bypasses RLS, so errors disappear and your whole database becomes reachable.

If a secret or service_role key leaked

Supabase's rotation procedure (Rotate a leaked or compromised key) starts with: "Don't rush. Fix the root cause of the leak before you rotate anything." Then:

  1. Create a new secret key in Settings > API Keys.
  2. Replace the compromised key everywhere your app uses it. Swap a legacy service_role JWT for a new secret key.
  3. Confirm every component now uses the new key.
  4. Retire the old key. Deleting a secret key can't be undone. For legacy keys, deactivate them in the same section (this is reversible).

Then move privileged calls into an Edge Function or server, and keep the key out of any VITE_ or NEXT_PUBLIC_ variable. Supabase's .env example warns: "Server-only. Never prefix these, or your bundler will ship the key."

Does Supabase's October 30, 2026 change fix this for me?

Not for tables you already have. From October 30, 2026, new tables in the public schema of existing projects need an explicit GRANT before the Data API can reach them. But Supabase is clear: "Existing tables are not affected in your project, they keep their current grants and stay reachable" (changelog). If an existing table was exposed without RLS, it stays exposed until you fix it. If you're now seeing 42501 on new tables, read our 42501 guide.

Check which key your own site ships

The free VibeShield passive check fetches your page's HTML and up to 10 same-origin JavaScript bundles. It reports which Supabase key types it finds (anon/publishable vs service_role/secret), other leaked secret patterns, and missing security headers. It doesn't log in, probe your database, write data or crawl. Secret values are never shown or stored.

It's for your own app only. You must confirm you own the app or are authorized to test it. A passive check cannot see your grants or RLS policies, so use the SQL above for those. A clean result doesn't mean your app is secure.

Want it re-run for you? The founding plan ($19/mo founding price, billed monthly via Stripe, cancel anytime) emails your first report within minutes of subscribing. After that it re-runs the same passive check every Monday and emails what's new, fixed and still open, with copy-paste fix prompts. Not ready to pay? There's a free waitlist on the homepage.

FAQ

Should I hide my anon key in environment variables?

Env vars are fine for configuration, but the publishable/anon key still ends up in the browser bundle. That's expected. Grants and RLS are the protection.

Is every key starting with eyJ dangerous?

No. Both legacy keys are JWTs. Check the role claim locally: anon is public by design, service_role must never ship.

My table returns an empty array. Am I safe?

Not necessarily. Postgres returns an empty result when no policy matches, but that only tells you about that one role and that one operation. Test inserts, updates, and signed-in users reading other users' rows.

Does the publishable key work differently from the anon key?

Both map to the anon role when nobody is signed in and to authenticated when a user is signed in. Supabase recommends publishable and secret keys because the legacy keys are being deprecated by the end of 2026.

Can someone use my secret key from a browser?

Supabase says a new secret key "doesn't work in a browser" and returns HTTP 401. It also warns that an attacker "can still use the key from other tools." So a leaked secret key is still a full exposure.

Does Lovable or Bolt check RLS for me?

Both have database security features. Lovable's docs say it "runs automated security checks against your database configuration, including Row Level Security (RLS) coverage." Bolt's docs describe a Security page that finds issues like "missing row-level security (RLS) or an access policy that's too open." Review their findings, and also run Supabase's Security Advisor.

Sources

All sources loaded on October 28, 2026. VibeShield is not affiliated with Supabase, Lovable or Bolt.