Supabase provides two families of keys. The first is public and can travel in the browser because Row Level Security policies filter it. The second is secret and overrides all those policies. It is an administration tool, very useful on the server, and it is the key that must never leave the server.
Two generations of keys
Supabase has introduced a new key system that is gradually replacing the old one:
| Use | New name | Legacy name | Can go in the browser |
|---|---|---|---|
| Public access, filtered by RLS | sb_publishable_... |
anon |
Yes |
| Administrator access, bypasses RLS | sb_secret_... |
service_role |
No |
The Supabase documentation states that the secret key goes through the service_role
Postgres role, which has the bypassrls attribute. Supabase announces the deprecation of
the legacy anon and service_role keys by the end of 2026. Creating the new keys does
not revoke the old ones: they stay active until you disable them under Settings, then
API Keys.
Supabase adds a useful safeguard: a secret key used from a browser is rejected with a
401 error, detected through the User-Agent header. It is a good safety net for the new
keys, and it does not cover a legacy service_role key or a key copied into a script or
a mobile app.
What the secret key is for
It is designed for processing that nobody triggers directly from a screen, or that must act beyond a user's rights:
- A payment webhook that updates a customer's subscription after a Stripe payment
- A scheduled job that archives old data or sends a report
- An administration function that creates an account, resets access or merges duplicates
- A migration or bulk import of data
In a Supabase app, this processing lives in Edge Functions. Supabase provides the
project keys to them as environment variables
(SUPABASE_SECRET_KEYS, or the legacy SUPABASE_SERVICE_ROLE_KEY), read with
Deno.env.get. The key stays on the server and never appears in the files sent to the
browser.
How it ends up in the wrong place
The most common scenario in an AI-built app is very simple. A feature hits an RLS policy: the list stays empty, the save fails. The tool tries to make the feature work and finds a fix that works immediately, by using the key that knows no rules. The screen works, the problem seems solved, and the administrator key ends up in the code delivered to every visitor.
Two other paths exist: a key pasted into a .env file pushed to GitHub, and a key
hard-coded in a test script left in the repository. In all three cases, the key becomes
an exposed secret.
How to check your app
- Search the exported code for the names:
service_role,sb_secret_,SERVICE_ROLE. Our guide on exporting the code explains how to get the repository if you have not already - Look at the code served in the browser: developer tools, Sources or Network tab. An administrator key must not appear in any JavaScript file or request
- Inspect the Git history, not just the current version. A key removed from the code is still readable in older commits
- List the server functions that use the secret key and check that each one verifies who is calling it. An Edge Function open to everyone that uses the secret key is equivalent to a table without a policy
- Take stock of the active keys in the dashboard, legacy ones included
A concrete example
An inventory app built with Bolt shows a supplier dashboard. The RLS policy stops one
supplier from seeing another's orders, and the administrator's summary screen stays
empty. The suggested fix replaces the public key with the service_role key in the
browser client. The screen fills up.
Six months later, a curious supplier opens their browser's developer tools, copies the key and can read, edit or delete all the data. The right fix would have taken an hour: an RLS policy for the administrator role, or a server function that computes the summary. The vibebusters Scan looks for exactly this kind of shortcut, in the current code and in its history.
The most common mistakes
- The secret key in the browser's Supabase client, to get around a policy
- The legacy
service_rolekey never disabled after moving to the new keys - The key in a versioned
.envfile on GitHub, sometimes in a public repository - An Edge Function without caller checks that uses the secret key
- The same key shared between the test environment and production
The takeaway
The secret key is a legitimate and often necessary tool. Handle it like the key to a safe: one place, on the server, with a clear list of the functions that use it and a plan to replace it. The Scan produces that inventory and, if a key has circulated, the Production Upgrade organises its replacement without interrupting the app.