Home / RLS / Lovable + Supabase security checklist · Em português

Supabase security · Lovable

Lovable + Supabase security checklist

Postgres 15 · Supabase · Lovable✓ code tested against a real database
TL;DR

Lovable writes the app; your Supabase policies decide who reads the data. Before you share the link: scan the live app for a service_role key, list tables the anon key can read, find always-true policies and anon-callable functions, then test signed out. Each check below is one query or one click.

01 Why Lovable apps need this

A Lovable app talks to Supabase straight from the browser with your anon key. That's normal, and it means Row Level Security is the only thing protecting your data. When the generated policies are missing or too open, anyone can read or write your tables.

That's what CVE-2025-48757 describes: insufficient RLS policies in Lovable-generated sites (through 2025-04-15) let unauthenticated attackers read or write database tables. Lovable disputes the CVE, saying each customer is responsible for their app's data. Either way, the fix is the same and it's in your database: the checks below.

Lovable apps are built with Vite, so any variable starting with VITE_ ends up in the JavaScript every visitor downloads.

02 Is the master key in your frontend?

The service_role key (or a new sb_secret_ key) bypasses every RLS policy. If it ships to the browser, anyone who opens your site can copy it and read or change your whole database. Builders put it there when it lands in a VITE_ variable or gets pasted into client code.

Scan your live app for it (free, paste the URL). Found one? Rotate it in the Supabase dashboard (Settings → API), move it to a server-only variable, redeploy and scan again.

.env

# fine in the browser: the URL and the anon / publishable key
VITE_SUPABASE_URL=https://xyz.supabase.co
VITE_SUPABASE_ANON_KEY=...  # or a sb_publishable_ key
# never with the VITE_ prefix: it would ship to every visitor
SUPABASE_SERVICE_ROLE_KEY=...

03 Which tables can anyone read?

Your anon key is public by design; RLS is the only thing between it and your data. Run this in the Supabase SQL editor:

check.sql

select c.relname,
       has_table_privilege('anon', c.oid, 'select') as anon_can_read,
       c.relrowsecurity as rls_on
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind = 'r'
order by 1;

Any row with anon_can_read = true and rls_on = false is public: close it.

04 RLS is on, but is it doing anything?

"RLS enabled" isn't the same as "data protected". A policy that is always true lets everyone in, and generated code often contains one. List them:

check.sql

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
  and (qual = 'true' or with_check = 'true')
order by 1, 2;

Keep a row only if that table is public on purpose (a catalog, a status page). Otherwise replace it with a policy scoped to the user. The RLS policy generator writes one per operation, tested.

05 Functions anyone can call

A security definer function runs as its owner and skips RLS. In the public schema it's also an API endpoint (/rest/v1/rpc/…). List the ones a signed-out visitor can execute:

check.sql

select p.proname
from pg_proc p join pg_namespace n on n.oid = p.pronamespace
where n.nspname = 'public'
  and p.prosecdef
  and has_function_privilege('anon', p.oid, 'execute')
order by 1;

For each one: is it meant to be public? If not, revoke execute on function public.name(...) from public, anon; or move it to a schema the API doesn't expose.

06 Prove it as a stranger

The dashboard and the SQL editor bypass RLS, so they always look fine. Test the way an attacker would: open your app in a private window, signed out, copy the anon key from the network tab, and request a private table:

terminal

curl "$SUPABASE_URL/rest/v1/orders?select=*" -H "apikey: $ANON_KEY"
# rows from other users → the policy is broken; [] → good

Then keep it fixed: npx airlock-rls in CI fails the build when a table ships without RLS or with an always-true policy, whoever (or whatever) wrote the migration.

◆ FREE · MIT · npm + GitHub Action

Catch this before it ships

airlock-rls is a CI gate that fails your build when a table ships exposed or a policy is permissive: the same class of bug, caught in CI against a database with your migrations applied, before it reaches prod.

npx airlock-rls→

Or start from nextjs-supabase-starter : auth + a table with RLS + an isolation test, so you start from a pattern that's already sealed.

FREE PDF

Grab the Supabase RLS cheat sheet

The golden rules, the footguns that leak in prod, correct policy snippets, and the isolation test — on one page.

FAQ

Lovable's security scan passed. Am I safe?

A passing scan is a good start, not proof. A policy can exist and still let everyone in (USING (true)). Run the always-true policy query and the signed-out curl test above; they show what an attacker would actually get.

Should I hide the anon key?

You can't: it ships in the app by design. It's safe when RLS is on and every policy restricts rows to the right user. The key to keep secret is service_role / sb_secret_.

Can I ask Lovable to fix the policies?

Yes, and then verify with the queries above. Generated fixes for permission errors sometimes open more than they should, like a policy that's always true or a broad grant.