Guide
Is a Supabase anon key safe?
A Supabase anon key is supposed to ship in the browser. It is safe only when row level security refuses reads and writes the signed-out visitor should not have. Seeing the key is not, by itself, a critical finding.
Supabase gives every project an anon key and a service_role key. Newer projects also ship an sb_publishable key and an sb_secret key. The anon and publishable keys identify the project to the API. Postgres policies are what decide whether that key can see a row. The service_role key and the sb_secret key bypass those policies. Neither privileged key may be compiled into a Lovable, Bolt, or Cursor app.
LeakWatch records an anon key or an sb_publishable key in the HTML or JavaScript as an info note. The note says the key is public configuration. The scan then sends one read-only GET, with limit=1, to tables it can discover, using that public key as the apikey header. A returned row with personal or payment columns is critical. Any other returned row is high. HTTP 200 with no rows is medium. If the key cannot read the tables, the row-access check passes. An sb_secret key or a service_role JWT is critical on its own and is not used to query the database. The stored example row has every value masked.
Rotating the anon key does not close an open table. Enable row level security and add a policy that matches the access you actually want, often auth.uid() for a user's own rows. Leave USING (true) off tables that hold private data.
Run a free scan of a deployed URL you have permission to test. The free report names every finding. The $19 Proof Report opens the location, evidence, and fix prompt. One scan is allowed per network every 30 days. LeakWatch is not affiliated with Supabase.
Questions
- Should I hide the Supabase anon key?
- No. The anon key is public client configuration. Hiding it in the frontend does not protect data. Row level security does. Do not replace it with the service_role key.
- When is an anon key a real problem?
- When a request that sends only that key can read a table. LeakWatch marks the key itself as info. A returned row with personal or payment columns is critical. Any other returned row is high. HTTP 200 with no rows is medium.
- Does LeakWatch store the rows it reads?
- No. The check uses limit=1, keeps the table name, and throws the row away. A service_role JWT or an sb_secret key found in the page is reported as critical and is not used to query the database.