How to check if your Lovable/Supabase app is leaking data
A step-by-step check you can do in 15 minutes: find your Supabase keys, see which tables have Row Level Security off, test what an anonymous visitor can read, and spot leaked secret keys.
Updated 2026-10-15
Only run these checks against a project you own. Everything below is read-only.
Why Lovable + Supabase apps leak
Apps built with Lovable (and Bolt, v0 and others that use Supabase) usually talk to the database straight from the browser. To do that, the frontend contains your Supabase project URL and the anon (or newer publishable) key. That is normal and by design — those keys are meant to be public.
What protects your data is Row Level Security (RLS): Postgres rules that decide which rows each request may see. If a table has RLS turned off, or has a policy that allows everyone, then anyone who copies the anon key out of your site can read that table. No hacking skills needed — just a browser.
Step 1: Find your project URL and anon key
Either:
- In the Supabase dashboard: Project Settings → API keys (the project URL is under Data API), or
- In your live app: open your browser's dev tools → Network, filter for
supabase.co, and look at a request to/rest/v1/. Theapikeyrequest header is the key your site ships to every visitor.
Step 2: See which tables have RLS off
In the Supabase dashboard:
- Table Editor labels tables without RLS (shown as “RLS disabled” or “Unrestricted”).
- Advisors → Security Advisor lists problems such as RLS being disabled on tables in the
publicschema. Fix everything it marks as an error.
Or run this in the SQL editor to list every table in the exposed schema and whether RLS is on:
select tablename, rowsecurity as rls_enabled
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;Any row with rls_enabled = false is readable (and possibly writable) by anyone with your anon key, unless you've removed the default grants.
Step 3: Look for policies that allow everyone
RLS being on isn't enough if a policy lets everybody through. List your policies:
select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;Watch for:
qualoftrueon aSELECTpolicy with roles{public}or{anon}— that table is public to the world. Fine for, say, published blog posts; not fine for profiles, orders or messages.UPDATE,INSERTorDELETEpolicies withtrue— anyone can change your data.- Policies that check
auth.role() = 'authenticated'only: any signed-up user can read everyone's rows.
Step 4: Test as an anonymous visitor
This is the real test: ask your API for data using only the public key, exactly as a stranger could. Replace the placeholders and the table name:
curl "https://YOUR_PROJECT_REF.supabase.co/rest/v1/profiles?select=*&limit=1" \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"- You get a row back: anyone can read this table. Fix it now (see the RLS guide).
- You get
[]: no rows are visible to anonymous users (or the table is empty — check it has data). - You get a permission error: the anonymous role has no access to the table at all. Good.
If you use the newer sb_publishable_… key, the apikey header alone is enough.
Step 5: Check Storage buckets
In Storage, any bucket marked Public serves its files to anyone who knows (or guesses) the URL. Keep user uploads such as ID documents, invoices or private photos in private buckets and hand out signed URLs. Listing and reading private files is controlled by policies on storage.objects — the RLS guide includes a per-user folder policy.
Step 6: Make sure no secret key is in your frontend
The anon key is fine to expose. These are not:
- The service_role key (or a new-style
sb_secret_…key). It bypasses RLS entirely. Older keys are JWTs starting witheyJ; decode the middle section — if it contains"role":"service_role", it must never be in the browser. - Third-party secrets: Stripe
sk_live_…, OpenAIsk-…, Anthropicsk-ant-…, AWS access keys, email API keys.
In Vite-based projects (Lovable, Bolt), any environment variable starting with VITE_ is compiled into the public JavaScript. In Next.js, the same applies to NEXT_PUBLIC_. To check a live site, open dev tools → Sources, search all files (Ctrl/Cmd + Shift + F) for sk_live, sk-, service_role and sb_secret.
If you find a secret key, rotate it first (removing it from code doesn't un-leak it), then move the code that uses it into a server-side function such as a Supabase Edge Function.
Step 7: Check for exposed files
Visit these on your live domain. Each should return a 404, not content:
/.env,/.env.local,/.env.production/.git/config- Source maps: open a JS file in dev tools and add
.mapto its URL.
What to do next
If anything above turned up a problem, the fix is usually: turn RLS on, add narrow policies, rotate any leaked key, and move secret work to the server. The RLS guide has copy-paste SQL for the common cases. After every big AI edit, re-check — builders sometimes create new tables or loosen policies to make a feature work.
Sealed — a short weekly security note for vibe coders
One common security mistake in AI-built apps, how to fix it in your builder, and one useful tool. No spam; unsubscribe any time.