Skip to content
SealMyApp

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/. The apikey request 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 public schema. 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:

  • qual of true on a SELECT policy 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, INSERT or DELETE policies with true — 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 with eyJ; decode the middle section — if it contains "role":"service_role", it must never be in the browser.
  • Third-party secrets: Stripe sk_live_…, OpenAI sk-…, Anthropic sk-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 .map to 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.