← All guides

Supabase RLS and the anon key: a 10-minute check without code

By Novruz Veliev, Senior QA Engineer · ShipClarity

Your Lovable or Bolt app talks to Supabase straight from the browser. That means the key it uses is sitting in your public JavaScript, and anyone can copy it. That's fine and by design - as long as Row Level Security is doing its job. If it isn't, that key is a read-write pass to your database.

You don't need to read SQL to check this. Below is the 10-minute pass I do on every Supabase-backed app before launch, in the order I do it. Most of it happens in the Supabase dashboard and in two browser windows.

In this article
  1. Make sure the public key is the public one
  2. Check that RLS is on for every table
  3. Read the policies, not just the switch
  4. The two-account test
  5. The logged-out test
  6. Storage buckets

1Make sure the public key is the public one

Supabase gives every project two kinds of keys. The public one (the anon key, or on newer projects a key starting with sb_publishable_) is meant to be in the browser. The secret one (service_role, or sb_secret_) skips RLS completely and must only live on a server.

AI builders sometimes paste the wrong one into the frontend while "fixing" a permissions error. The app suddenly works, and nobody notices why.

How to check

Open your live site, then DevTools → Sources (or Network), and search the loaded JavaScript for sb_secret_. For older-style keys, copy any long string starting with eyJ that sits next to your Supabase URL and paste it into a JWT decoder: the role field must say anon, never service_role. If it says service_role, rotate that key in the dashboard today, before anything else on this list.

2Check that RLS is on for every table

A table with RLS switched off is open to anyone holding the public key: read, insert, update, delete. Tables created later in a project (a "feedback" table, a "logs" table, whatever the AI added last week) are the usual gap.

How to check

Supabase dashboard → Table Editor. Tables without RLS carry a warning label next to their name. Then open Advisors → Security Advisor and read every error it lists - "RLS disabled in public" is the one to fix first. It takes a minute and catches most of the obvious cases.

3Read the policies, not just the switch

RLS on with a policy that says "everyone can read everything" is the same as RLS off, just quieter. The Security Advisor does not flag it, because technically RLS is enabled.

How to check

Authentication → Policies lists every policy per table. You're looking for two patterns: a policy for the anon or public role on a table that holds user data, and a condition that is just true. For tables like profiles, orders or messages, the condition should mention the logged-in user, usually something like auth.uid() = user_id. If a policy reads like "true" in plain words, ask yourself whether a stranger should see every row of that table.

Views and database functions deserve a separate look: a view can return rows that the table's own policies would hide. If the Security Advisor mentions a "security definer view", treat it like an RLS gap.

4The two-account test

This is the check that matters most, and the one the dashboard can't do for you. Policies can look right and still leak because of one wrong column name.

How to check

Sign up as user A in a normal window and create something private: a note, an order, a profile field. Copy the URL of that item. Now open a private window, sign up as user B, and paste A's URL. Then try the same thing on every page that shows an id in the address bar. B should see an error or an empty page, never A's data. Also check lists: B's dashboard shouldn't show A's items mixed in.

If you want to go one step further without code, repeat it for editing: as B, open A's item and try to save a change. A policy for reading and a policy for updating are separate things, and it's common to get one right and forget the other.

5The logged-out test

Log out completely and walk through the app as a stranger: open the pages that only members should see, and watch the Network tab in DevTools. Requests to /rest/v1/ that come back with real rows while you're logged out tell you exactly which table is readable by anyone.

6Storage buckets

Uploaded files have their own rules, separate from table RLS. A bucket marked Public serves every file to anyone who has or guesses the link.

How to check

Storage in the dashboard: for every bucket marked Public, ask whether its files are really public (logos, product images) or private (invoices, ID photos, user uploads). Private files belong in a private bucket, with a policy that ties each file to its owner.

Want a second pair of eyes on it?

Everything above you can do yourself in about 10 minutes, and it catches the most common gaps. What it doesn't cover: role changes, invite flows, admin pages, payments and the edge cases between them.

Run the free outside scan See what a full audit covers

Checking one user against another user's data is one of the manual checks in a Pre-Launch Audit, done with real test accounts and screen recordings of every step.