Checklist
Is the Supabase anon key safe to expose?
Short answer. Supabase's docs say the anon key, and the newer publishable key, are meant to be in the browser. They are not a secret. That only holds when row level security is on and the grants are tight. The service_role key and the newer secret key bypass those policies. Those must stay on the server.
Updated 26 Oct 2026
This is a practical checklist, not a guarantee. It is not a penetration test, a compliance audit, or proof that an app is safe to launch.
The short version is the design Supabase documents, not a promise about your project. If row level security is off, or the grants are wide open, the anon key is a public handle on a public table. The setup steps are how to set up Supabase row level security. The pass after the policies exist is the RLS checklist.
Which key is which
Supabase's API key docs describe four keys. Two are meant for code you ship. Two are not.
| Key | Format | What Supabase says |
|---|---|---|
| Publishable key | sb_publishable_... | Low privilege. Safe to put in a web page, a mobile app, or source. It only reaches what row level security allows. |
| Anon key | A long JWT | The legacy publishable key. Same idea: it identifies the project and is not a secret. |
| Secret key | sb_secret_... | Elevated. Bypasses row level security. Server, Edge Function, or another backend you control. |
| Service role key | A long JWT | The legacy secret key. Same bypass. Supabase says to prefer a secret key where you can. |
A publishable key with no signed-in user acts as the anon Postgres role. The same key, with a user signed in through Supabase Auth, acts as authenticated. A secret key acts as service_role, which has the bypass-RLS attribute. Policies you write for anon and authenticated do not apply to it.
Creating a publishable or secret key does not revoke the legacy keys. Supabase says both systems work at the same time until you disable the legacy keys under Settings, then API Keys.
How to tell an anon JWT from a service_role JWT
Both legacy keys are JWTs, so both start with eyJ. The prefix is not the difference. The payload is.
A JWT is three base64url segments separated by dots. The middle segment is the payload, and it is encoded, not encrypted. Decode that segment locally and read the role field.
roleofanonis the anon key. Supabase expects this in the client.roleofservice_roleis the service role key. It does not belong in the client, in aVITE_orNEXT_PUBLIC_variable, or in a public repository.- A value that starts with
sb_publishable_is the newer public key. A value that starts withsb_secret_is the newer secret key.
Do not paste the key into a website to decode it. Decode it on your own machine. The free passive check reports an anon JWT it finds in a public script as "Info: anon key present, make sure RLS is on." It treats a service_role JWT as a failure, and it shows only a four-character preview.
When the anon key is the wrong thing to worry about
Supabase's securing-your-data page says you can keep data protected from a frontend if you turn on row level security, write least-privilege policies, and use the publishable key. The same page says the secret and service role keys are never safe to expose because they bypass row level security.
Two other gaps look like "the anon key leaked" and are not:
- Row level security is off. Supabase says that once it is on, the publishable key sees no rows until a policy allows them. Off means the grants decide, and on existing projects a new
publictable can already grant every privilege toanonandauthenticated. - The policy is
using (true)for a role that should not see every row. Supabase says that form grants every row the role can already reach. Use it only for data you mean to be public. - A signed-in check with no owner column. The
authenticatedrole is every logged-in user, not "this user." Compare(select auth.uid())to an owner column, or to a membership row.
The API keys in the frontend page is the wider pass for Stripe, OpenAI, and the other keys that are secrets even when Supabase's publishable key is not.
If the service role key leaked
The full sequence, including logs and the Lovable, Bolt, and v0 paths, is Supabase service role key exposed: what to do now. Treat it as compromised the moment it has been in the browser, in git, in a chat transcript, or in a built JavaScript file. Rotating it later does not unread a copy someone already saved.
1. Remove the value from client code, from any VITE_, NEXT_PUBLIC_, or PUBLIC_ variable, from the chat, and from the repository before a replacement is written into a file the browser can see. 2. In Settings, then API Keys, create a new secret key and put it only on the server. If the leaked value was a JWT service_role key, replace it with that secret key. 3. Confirm every server, Edge Function, and webhook uses the new key, then retire the old one. Delete a leaked secret key. Deactivate a legacy service_role key. Creating a new key beside the old one does not retire it, and the old key keeps working until that step finishes. 4. Re-test with the publishable key and two users. The server path that needed the bypass should be the only place still using a secret key, and it should be small.
Supabase also notes that a secret key bypasses row level security only when the request does not carry a user access token. A server client that then calls signUp, or an SSR helper that copies the user's session onto the service client, can stop acting as service_role. That is a separate bug from a leaked key. Their troubleshooting note says to use admin.createUser() when the service client must create a user, and to keep that client separate from the user-session client.
FAQ
Can I put the anon key in a public GitHub repo?
Supabase's docs say the anon key and the publishable key are safe to expose in source, a web page, or a CLI, because anyone can read them. The condition on the same pages is row level security plus least-privilege grants. The service role key is not in that category.
The app shipped both eyJ keys. Which one do I rotate?
Decode the payload. Rotate and remove the one whose role is service_role, and any sb_secret_ value. Leave the anon or publishable key in the client, then prove the policies. If you cannot tell them apart, rotate both server keys and confirm the client bundle no longer contains a service_role payload.
Does enabling RLS protect a table the service role reads?
No. Supabase says service_role has the bypass-RLS attribute, and that adding service_role to a policy does nothing. The protection is keeping that key off the client. RLS protects requests made with the publishable or anon key.
Where do Lovable and Bolt put this key?
Lovable says the publishable key is a VITE_ value in .env, and the service role name is reserved for server code. Bolt's secrets tab is for server functions. The project walkthroughs are how to secure a Lovable app and how to secure a Bolt.new app. Supabase is the directory entry.
Sources
Checked 26 Oct 2026.
Informational only. This page describes checks and vendor documentation. It is not a security audit, a penetration test, or a certification, and it is not a guarantee.
Newsletter
A short weekly note on new tools and security checklists. No spam, unsubscribe anytime.