Skip to content
Heartscale

supabasePublished on · Heartscale team

Supabase key exposed: which key is safe in the browser?

The Supabase publishable key (sb_publishable_..., or the legacy anon key) can live in the browser, as long as every table has RLS enabled. The secret key (sb_secret_..., or the legacy service_role key) never can: it bypasses RLS and opens the whole database. If the secret key leaked, rotate it before anything else.

Heartscale’s recommendation: search for the secret key in your code, your published build and your git history before you conclude it did not leak. If it shows up in any of them and the app has real users, rotate it right away and get your tables and logs reviewed.

The rules below follow Supabase’s official documentation on API keys, checked in October 2026. Everything here applies to your own project.

The four Supabase keys

Supabase runs two key systems side by side: the new one and the legacy one. Each has a public key and a secret key.

Key Format Safe in the browser? Respects RLS?
Publishable sb_publishable_... Yes Yes
anon (legacy) Long JWT starting with eyJ Yes Yes
Secret sb_secret_... Never No, bypasses it
service_role (legacy) Long JWT starting with eyJ Never No, bypasses it

The docs say a secret key must never be in a browser, a shipped app, a public document or source control, not even on localhost. They also state that Supabase plans to deprecate the anon and service_role keys by the end of 2026.

Why the publishable key can be public

Apps built with Lovable, Bolt or Base44 usually talk to the database straight from the browser. The publishable key ships with the site, and any visitor can see it.

That is by design. Without a signed-in user, the publishable key reaches the database as the anon role. With a signed-in user, as authenticated. Your RLS policies decide what each role can read or change. That is why Supabase asks you to enable RLS on every table before deploying.

The real risk with the publishable key is tables without RLS or with policies that are too open. Rotating the key does not fix that. The step-by-step guide is in Supabase RLS: how to configure and test it.

The secret key maps to the service_role role, which has the BYPASSRLS attribute. Policies never apply to it. Whoever holds the secret key holds the database.

Step 1: find out which key your code uses

Open your app’s repository and search for the keys and the usual variable names:

grep -rn --exclude-dir=node_modules -E "sb_secret_|service_role|SERVICE_ROLE_KEY|SUPABASE_SECRET" .
grep -rn --exclude-dir=node_modules -E "sb_publishable_|anon|PUBLISHABLE_KEY" .

For each hit, ask where that code runs:

  • Browser: files in src/, React components and any variable with the VITE_ prefix. Vite warns that VITE_ variables end up in client code and should not hold API keys. Only the publishable key belongs here.
  • Server: Edge Functions in supabase/functions/, server routes and scripts that run outside the browser. The secret key can be used here, read from an environment variable and never written in the file.

If the hit is a long JWT starting with eyJ, compare it with the keys under Settings > API Keys in the Supabase dashboard. That tells you whether it is anon or service_role.

Also check what actually ships to the browser. Build your app and search the output:

npm run build
grep -rn -E "sb_secret_|service_role" dist/

Any hit here is a secret key published on your site.

One extra signal: Supabase checks the User-Agent header and returns 401 Unauthorized when a secret key is used from a browser. If your app gets 401 on calls that should work, check whether the secret key ended up in the front end. The docs are clear that this block is only a safeguard: a leaked key still works from other tools.

Step 2: search your git history

Removing the key from the file does not remove it from the history. Search every branch:

git log --all --oneline -S "sb_secret_"
git log --all --oneline -G "service_role|sb_secret_|SERVICE_ROLE_KEY"

-S lists commits that change the number of occurrences of the string. -G lists commits whose diff adds or removes a line matching the pattern (git log). To see the change in a commit it finds, run git show <hash>.

If the key shows up in any commit of a repository that was ever public, or shared with anyone, treat it as leaked.

Step 3: the secret key leaked, do this in order

The order comes from the Supabase docs. Rotation comes before cleaning the code, because the old key keeps working until you delete it.

  1. Fix the root cause. Take the key out of the front end, out of VITE_ variables and out of any committed file. Move the operation that needed it to an Edge Function or other server code.
  2. Create a new secret key under Settings > API Keys. The docs recommend one secret key per backend component, so a future leak means rotating only one.
  3. Replace the old key everywhere it is used: Edge Functions, servers, scheduled jobs and integrations.
  4. Confirm that every component uses the new key.
  5. Delete the old key. Deleting a secret key cannot be undone, so do it only after step 4. For the legacy service_role key, the option is to deactivate it in the same section, which can be reversed.

Two points about legacy keys:

  • Creating new keys does not deactivate anon or service_role. Both systems work at the same time until you deactivate the legacy keys.
  • In Edge Functions, SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY hold the legacy keys. The docs point to SUPABASE_SECRET_KEYS or the @supabase/server SDK instead. If the browser still uses the anon key, switch it to the publishable key before deactivating the legacy keys, so the app keeps working.

Step 4: clean the history, if it makes sense

GitHub also puts revoking or rotating the credential first. Rewriting history comes later and has a cost:

  • commit hashes change, and anyone with an old clone can push the key back;
  • forks and old pull requests keep the content;
  • branch protection against force pushes must be turned off for a while.

Once the key is rotated and deleted, the value in the history no longer opens anything. Many teams stop here. If you decide to clean up, GitHub documents git-filter-repo with the --sensitive-data-removal option.

Step 5: review RLS and the Security Advisor

The secret key bypasses RLS, so good policies did not protect anything while it was exposed. Rotation is still a good moment to review the publishable key, which stays on your site:

  • check that every exposed table has RLS enabled and per-operation policies;
  • remember that table grants are checked before RLS;
  • open the project’s Security Advisor and resolve what it lists.

Step 6: read the logs for that period

In the Supabase dashboard, open Logs and pick the API Gateway type (Logs). Set the Time Range to the period when the key was exposed and filter by Method and Pathname. Look for writes and deletes your app does not make, traffic spikes and tables nobody usually queries. Use Download logs to keep what you find.

Log retention depends on your plan. Do this soon, before older entries expire.

If the logs show improper access to personal data and your app has users in Brazil, it may be a security incident under the LGPD, with a deadline to notify the ANPD. See LGPD for AI-built apps with users in Brazil.

What if only the publishable key leaked?

It is meant to be public, so the leak alone does not call for rotation. What matters is what it can reach. Query your tables with the publishable key and no signed-in user: whatever comes back is what any visitor sees. If data comes back that should not, fix RLS or the table grants.

In our reviews, we run that query on every exposed table, first with the publishable key and no login, then as a regular user trying to read another user’s data.

For a wider review of your app, see the AI-built app security page.

Do it yourself or hire help?

If your search did not find the secret key, this guide is enough. The table shows when a rotation needs more care.

Situation Path
Only the publishable key is on the site and every table has RLS enabled Follow step 5 and test your tables without a login
The secret key leaked and the app already has real users Rotate it now, in the order of step 3, and get your tables and logs reviewed
The secret key is used by Edge Functions, scheduled jobs and integrations you cannot list Bring in a team to map every use before you delete the old key
The app has real users and the logs show access your app does not make Request a Heartscale diagnostic and check the LGPD notice deadline if you have users in Brazil

Request a Heartscale diagnostic

Our team searches your code, your published build and your git history for keys, checks where each one runs and tests your tables’ RLS with the publishable key. If a secret key is exposed, you get the rotation order for your setup and the fix for the code that depended on it. A review reduces the risk; it does not remove it.

The launch-price diagnostic costs US$ 19 (was US$ 79) and is credited toward the fix if you go ahead. We only work on apps whose owners can prove they own them.

Request the diagnostic

Frequently asked questions

Frequently asked questions

Is it a problem that my Supabase anon key is visible in my site's code?

No, as long as every exposed table has RLS enabled with correct policies. The anon key, now called the publishable key, is designed to live in the browser. Your database policies decide what it can reach.

What happens if my Supabase service_role key leaks?

Anyone holding it can read, change and delete any data in the project, because it bypasses RLS. Fix the cause of the leak, create a new key, replace it everywhere, delete the old one and review your logs.

Does deleting the key from my code and committing fix the leak?

No. The key stays in the git history and in every clone or fork of the repository. The first step is rotating the key in Supabase. Cleaning the history comes later and does not replace the rotation.

How do I tell if the key in my code is the publishable or the secret one?

By the prefix. Publishable keys start with sb_publishable_ and secret keys with sb_secret_. Long keys starting with eyJ are the legacy anon or service_role keys; compare them with the ones under Settings > API Keys in the dashboard.

Can I put my Supabase secret key in a VITE_ environment variable?

No. Vite ships every VITE_ prefixed variable to the browser bundle. The secret key belongs on the server only, for example in an Edge Function.

Is it worth hiring someone to handle an exposed Supabase key?

It is when the secret key leaked in an app with real users, when several parts of the backend use it, or when nobody on your team reads the code. Heartscale runs a launch-price diagnostic for US$ 19 (was US$ 79), credited if you hire the fix within 15 days. Rotate the key first, without waiting for the diagnostic.

Start with the diagnostic.

The diagnostic costs US$ 19, paid by payment link. We deliver the report in 2 business days and walk you through the findings in a meeting. The fee is credited if you hire the service within 15 days. Everything we set up stays in your name.

Reduces the risk, does not eliminate it. We only work with the app’s owner, with proof of ownership.