Abbi Labs

Guides

How to check if your Supabase tables are readable by anyone

By Pritesh Ghodge, security engineer · Last updated 30 October 2026

If your app was built with Lovable, Bolt, v0 or Cursor on Supabase, your browser talks straight to your database. That's normal. The question is what an anonymous visitor can read when they do the same thing. Here's how to check in about a minute, and how to fix it.

The anon key being public is fine

Your Supabase URL and anon key (newer projects call it the publishable key, starting sb_publishable_) are meant to be in your front-end code. Anyone can open DevTools and copy them. That's by design.

What decides whether that key can read a table is Row Level Security (RLS) and the policies on the table. If RLS is off, or a policy allows everyone, the anon key can read the table.

Two keys must never be in the browser: the service_role key and sb_secret_ keys. They bypass RLS completely. If either is in your bundle, rotate it first, then move that code to a server route or edge function.

The 60-second test

Copy your project URL and anon key from your site's JavaScript (or Supabase → Project Settings → API), then run this for each table:

curl "https://YOUR-PROJECT.supabase.co/rest/v1/profiles?select=*&limit=1" \
  -H "apikey: YOUR_ANON_KEY" \
  -H "Authorization: Bearer YOUR_ANON_KEY"

Try the tables that usually hold personal or payment data first: users, profiles, orders, payments, customers, subscriptions, messages, invoices.

Only run this against a project you own.

"RLS enabled" isn't the same as protected

This is the trap that catches people who did turn RLS on. An AI agent hits a "permission denied" error while building, and the quickest fix it finds is a policy like this:

create policy "Enable read access for all users"
  on public.profiles for select
  using (true);

RLS is still on and the dashboard still shows a shield, but using (true) means every row is readable by everyone, including the anon key. Read each policy's using clause and the role it applies to.

The curl test only shows reads. To check INSERT and UPDATE policies too (for example with check (true)), run the free Supabase RLS policy checker: one read-only SQL query, paste the result, and it lists every open policy with fix SQL. It runs in your browser; nothing is uploaded.

The fix

Turn RLS on and allow each signed-in user to read only their own rows. Replace user_id with whichever column holds the owner's id:

alter table public.profiles enable row level security;

drop policy if exists "Enable read access for all users" on public.profiles;

create policy "own rows"
  on public.profiles for select
  to authenticated
  using ((select auth.uid()) = user_id);

Add matching policies for insert, update and delete if your app needs them. Then run the curl test again and confirm you get [].

If you're in Lovable or Bolt, you can paste a prompt like: "Enable RLS on every table in the public schema. Remove any policy that uses using (true) for anon or public. Add policies so users can only read and change rows where user_id matches their auth id."

Don't forget storage buckets

Supabase Storage has the same problem with files. A bucket marked public lets anyone with the URL download its files. If a bucket holds user uploads (ID photos, invoices, avatars you'd rather keep private), make it private and serve files with signed URLs. Check the policies on storage.objects too.

What an outside test can't tell you

Testing with the anon key shows what an anonymous visitor can read. It doesn't show whether signed-in user A can read user B's rows. For that, create two test accounts, sign in as one, and try to fetch the other's records by id. You should get nothing back.

It also only covers the tables you test. Unusual table names won't show up in a check of common ones.

Keep checking after every change

Policies drift. A table that was locked down last week can be opened up by a single agent "fix". Re-run the test after every deploy, or let a tool do it.

A reader on X suggested a cheap guardrail that's worth copying: make your CI fail the deploy if any table in the public schema has RLS switched off, or has a read policy that's just using (true). Run this as a step before deploy, with your database connection string stored as a CI secret (never in front-end code):

OFF=$(psql "$DATABASE_URL" -tA -c "select tablename from pg_tables where schemaname = 'public' and not rowsecurity")
OPEN=$(psql "$DATABASE_URL" -tA -c "select tablename || ' / ' || policyname from pg_policies where schemaname = 'public' and cmd in ('SELECT', 'ALL') and qual = 'true'")
if [ -n "$OFF$OPEN" ]; then
  echo "RLS off: $OFF"
  echo "Open read policies: $OPEN"
  exit 1
fi

If a table is meant to be public (a list of blog posts, say), add it to an allow-list in the query rather than removing the check. This catches the "AI turned RLS off to fix an error" regression before it ships. It can't catch a policy that looks strict but compares the wrong column, so keep the two-account test above too.

Abbi Labs runs this check on domains you've verified you own: it finds the Supabase URL and anon key your site already serves, tries to read one row from common tables like the ones above, checks for public storage buckets, and stores only a yes or no. It also looks for leaked secret keys in your JavaScript and exposed .env and .git files. The first scan is free, and Monitor re-scans weekly and after every deploy.

The free quick check needs no sign-up: paste your URL and it tests the same common tables with the public key your site serves, plus secret keys in your JavaScript, /.env, /.git and security headers. Storage buckets and source maps are in the full scan after you verify your domain.

Check my site free, no sign-up →

FAQ

Is it a problem that my Supabase anon key is visible in my site's JavaScript?

No. The anon (or sb_publishable_) key is designed to be public. What protects your data is Row Level Security and table grants. The service_role and sb_secret_ keys are different: they bypass RLS and must never reach the browser.

RLS is enabled on my table. Am I safe?

Not necessarily. A policy like using (true) for the anon or public role keeps RLS switched on while letting anyone read every row. Check what each policy allows, not just whether RLS is on.

Can an outside test prove my app is secure?

No. Testing with the anon key shows what an anonymous visitor can read. It can't show whether one signed-in user can read another user's rows. Test that separately with two real test accounts.

Does Abbi Labs store my data when it checks tables?

No. It requests at most one row per table and stores only a yes or no for whether rows came back.

Related guides

Weekly AI-app security checklist

One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.