🐕 LatchDog

← Guides

How to monitor your Supabase app for leaked keys and open RLS

Short answer: check three things, and keep checking them after every change. (1) No secret key is shipped in the JavaScript your site serves. (2) No table or view answers to your public anon key unless it's meant to be public. (3) Something re-runs those checks after every deploy and tells you when the result changes. Below is how each one goes wrong in AI-built apps, the free tools that cover it, and where an outside monitor like LatchDog fits in.

1. How keys end up in your frontend bundle

Lovable, Bolt, v0 and most Cursor/Vite/Next.js setups build a JavaScript bundle that every visitor downloads. Anything compiled into it is public, including:

To check by hand, open your live site, view the loaded scripts in DevTools, and search for service_role, sb_secret_, sk_live_, sk-proj-, sk-ant-. A JWT starting with eyJ can be decoded at any JWT debugger; look at its role claim.

2. anon key vs service_role key: which one is a leak?

KeyIn the browser?Why
anon JWT or sb_publishable_...ExpectedDesigned to be public. It can only do what your Row Level Security (RLS) policies allow.
service_role JWT or sb_secret_...NeverBypasses RLS entirely: full read/write to every table.

So "my anon key is visible" is not a leak in itself. It becomes one only when a table or view is readable through it. If a service_role or sb_secret_ key leaked, follow Supabase's order: fix the code that exposed it, create a new secret key, deploy it everywhere, confirm nothing uses the old one, then delete it (or deactivate legacy JWT keys). Rotating first and fixing later just leaks the new key.

3. Checking RLS (and the view trap)

alter table public.profiles enable row level security;
create policy "owner reads own profile" on public.profiles
  for select to authenticated using ((select auth.uid()) = user_id);
alter view public.profile_list set (security_invoker = true);

Changing on October 30, 2026: new tables need an explicit grant

Supabase is changing a default (Supabase changelog). Until now, every new table in public got select, insert, update and delete granted to anon, authenticated and service_role, so it was reachable through the Data API the moment it was created. That default is already off for new projects. From October 30, 2026 it is enforced on existing projects too: a new table in public isn't reachable through the Data API (supabase-js, /rest/v1/, GraphQL) until you grant access. Existing tables keep their current grants. Apps that only use a direct Postgres connection are not affected.

-- Supabase's recommended order for a new table
grant select on public.your_table to anon;
grant select, insert, update, delete on public.your_table to authenticated;
grant select, insert, update, delete on public.your_table to service_role;
alter table public.your_table enable row level security;
create policy "users can read their own rows" on public.your_table
  for select to authenticated using (auth.uid() = user_id);

4. Free tools, and when they're enough

When that's enough: if you deploy rarely, run the builder's scan on each publish, and run Security Advisor after each migration, you may not need anything else. These tools are free, and the builder and Advisor run where your code and schema live.

What they don't do: watch the deployed site over time. They run when you remember to run them, or on publish for that builder only, and they don't tell you that a key showed up in last Tuesday's bundle, or that a table that was closed last week answers to the anon key today.

5. Monitoring over time

AI edits regress things silently: a prompt to "fix the profile page" can add a view, drop a policy or inline a key. Monitoring means the same outside-in checks on a schedule and after each deploy, compared with the previous result, so you only hear about changes. You can script this yourself (a CI job that greps the built bundle for secret prefixes and runs anon-key limit=1 queries against your tables), or use a service.

What LatchDog does here (and doesn't)

A sensible setup: Security Advisor after migrations, your builder's scan on publish, and an outside monitor on the live site so regressions between those moments don't go unnoticed.

Check your app now

Free, read-only public check: no sign-up, no card. Headers and HTTPS only. Deep scan (leaked keys, open Supabase tables, .env/.git) after you verify you own the domain.