FlowCheck
All guides

Supabase anon key in the browser: what is safe, and what is a leak

The anon key is supposed to be public. The service_role key is not. The difference is row level security, and you can check it without writing data.

A Supabase client in a Lovable or Cursor app ships the project URL and the anon key to the browser. That is the documented setup. The same bundle sometimes also contains the service_role key because it was pasted into a component while debugging. That key bypasses row level security.

What a passive check is allowed to do

Read the public JavaScript. Decode JWTs far enough to see the role claim. If the role is service_role, treat the key as compromised: rotate it in the Supabase dashboard and move the new value to a server-only environment variable. If the role is anon, leave the key where it is and ask a different question: can that key read tables?

  • Send a GET with the anon key to common tables, limit 1, no writes.
  • HTTP 401 or 403 means the table refused an anonymous read.
  • HTTP 200 and a JSON array means the table is readable without a user session. Turn on RLS before there is real data in it.

Publishable Stripe keys (pk_) belong in the browser. Secret keys (sk_live_, rk_live_) do not. The same scan should flag those, and the fix prompt should say to rotate the key, not to hide the finding by encoding it.