To fix broken RLS on a Lovable app: open your Supabase dashboard, enable Row Level Security on every table that holds real data, then write an explicit policy for each operation (select, insert, update, delete) that checks auth.uid(). Then — and this is the step everyone skips — verify it actually blocks an anonymous request, because Supabase’s own advisor confirms a policy exists, not that it works. You can do all of this yourself, for free, in about twenty minutes.
Row Level Security (RLS) is the Postgres feature that decides, row by row, who is allowed to read or write a piece of data. When it’s misconfigured on a Supabase-backed app, anyone with your public API URL — which ships in your browser bundle — can query your tables directly and read data that was supposed to be private. This is the most common critical issue we find on AI-built apps, so let’s fix it properly, starting with the free path.
Step 1 — Find which tables are exposed
In the Supabase dashboard, open Advisors → Security Advisor. It will list any table where RLS is disabled outright — start there. Then open the Table Editor and look at every table that holds user or business data: profiles, orders, messages, submissions, anything private. Note which ones have the little “RLS disabled” warning.
But don’t stop at the toggle. The real test is to behave like an anonymous visitor. Your project ref, URL, and anon key are all public (they’re in your client bundle), so you can run this from a terminal:
curl 'https://YOUR_REF.supabase.co/rest/v1/profiles?select=*' \
-H "apikey: YOUR_ANON_KEY"
# Leaking: [ { "id": 1, "email": "real@user.com", ... }, ... ]
# Safe: [] (RLS blocked the read)
# Safe: 401 / permission deniedIf real rows come back, that table is readable by the entire internet. If you get an empty array or a permission error, RLS is doing its job on reads (you still need to check writes).
Step 2 — Enable RLS on the table
Open the SQL Editor and turn RLS on for each exposed table. With RLS enabled and no policy, Postgres denies everything by default — which is safe, but will break legitimate access until you add policies in Step 3, so do these together.
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
Step 3 — Write a policy per operation
A single “enable RLS” is not a fix. You need an explicit policy for each operation your app performs. The most common pattern is “a user can only touch their own rows,” expressed by comparing the row’s owner column to auth.uid() (the ID of the logged-in caller). Here is a complete before/after for a profiles table keyed by a user_id column.
Before — the silent leak
Either RLS is off, or there’s a lazy catch-all policy that a builder generated to “make it work”:
-- DANGER: this satisfies the advisor but blocks nothing CREATE POLICY "allow all" ON public.profiles FOR SELECT USING (true);
USING (true) means “this row is visible for every request” — RLS is technically enabled, a policy technically exists, and your entire table is still public.
After — policies that actually enforce ownership
-- Read only your own row CREATE POLICY "read own profile" ON public.profiles FOR SELECT USING (auth.uid() = user_id); -- Insert only rows you own CREATE POLICY "insert own profile" ON public.profiles FOR INSERT WITH CHECK (auth.uid() = user_id); -- Update only your own row CREATE POLICY "update own profile" ON public.profiles FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); -- Delete only your own row CREATE POLICY "delete own profile" ON public.profiles FOR DELETE USING (auth.uid() = user_id);
Note the two clauses: USING filters which existing rows a query can see or affect; WITH CHECK validates the new values on an insert or update so a user can’t write a row owned by someone else. Public-read tables (say, a published blog post) can keep a deliberate USING (true) on SELECT only — the sin is applying it to private data or to writes.
Step 4 — Verify it actually blocks the request
This is the step that separates a real fix from a false sense of safety. Re-run the exact anonymous request from Step 1. It must now return an empty array or a permission error, not data. Then test the same request while authenticated as a real user and confirm they still see their own rows — you want the leak closed and the app working.
USING (true); a request that comes back with real rows cannot.Why this keeps happening on AI-built apps
This isn’t a knock on Lovable or Supabase specifically. Supabase actually does the responsible thing: it ships RLS-by-default on new tables and includes a Security Advisor that warns you when RLS is off. The gap is that an AI builder, prompted to “make the data load,” will sometimes disable RLS or generate a permissive policy to clear an error — and the advisor, which checks for the presence of a policy, stays quiet.
The consequences are documented. CVE-2025-48757 was a Lovable RLS misconfiguration that exposed 303 endpoints across 170 projects. An estimated 70% of Lovable apps fail the anonymous-read RLS check on at least one table. The pattern is consistent enough that if you built with an AI tool and haven’t hand-checked RLS, you should assume a table is open until you’ve run the request in Step 4.
Common mistakes to avoid
- ›Enabling RLS but forgetting writes. A read policy without matching insert/update/delete rules can leave your table writable by anyone — the more dangerous failure of the two.
- ›Leaving
USING (true)on private data. RLS enabled, a policy present, and the table still readable by everyone. - ›Using the
service_rolekey in client code. That key bypasses RLS entirely; it belongs only in server-side code and must never ship in a bundle. - ›Public storage buckets. RLS on your tables won’t help if an uploads bucket is set to public and lists everyone’s files.
If you’d rather we did it
The free path above is genuinely the right first move — for most apps it’s twenty minutes of work and no cost. If you’d rather not write SQL, or you want a second pair of eyes on a fundraise-critical app, the Fix puts a senior dev on it: we write and test the policies, rotate anything that leaked, and re-scan to show the before/after. And whether you fix it yourself or we do, finish by running the free scan once more to confirm the anonymous request now returns nothing.
FAQ
How do I know if my Lovable app's RLS is actually broken?+
Don't trust the toggle — test it. Take your project's public anon key and URL (both are in your client bundle, so they're already public) and make an anonymous GET against a table that should be private, e.g. curl 'https://<ref>.supabase.co/rest/v1/profiles?select=*' with the anon key as the apikey header. If it returns rows of real data instead of an empty array or a 401, your RLS is not enforcing. This is the exact check Prodable's free scan automates, and an estimated 70% of Lovable apps fail it.
Isn't the Supabase Security Advisor enough?+
The Security Advisor is genuinely useful and you should turn it on — it flags tables where RLS is disabled entirely. But it checks that a policy exists, not that the policy actually blocks the read or write. A table can have RLS enabled and a permissive USING (true) policy that lets anyone read everything; the advisor is satisfied, but the data is wide open. Correctness is only provable by making the request and seeing what comes back.
Will enabling RLS break my app?+
It can, if you enable RLS without adding policies — because with RLS on and no policy, the default is to deny everything, so legitimate reads start returning empty. That's why the steps here pair enabling RLS with writing an explicit policy for each operation your app actually needs (select, insert, update, delete). Test as a logged-in user after each change. Breaking reads for everyone is safer than leaking them to everyone, but the goal is neither.
Is it safe that my Supabase anon key is in the browser?+
Yes — the anon (publishable) key is designed to be public and is meant to ship in your client. Your security does not come from hiding it; it comes from RLS policies enforcing who can see what once a request arrives with that key. The dangerous key is the service_role key, which bypasses RLS entirely — that must never appear in client code, and our scan flags it as critical if it does.
Can Prodable just fix the RLS for me?+
Yes, but try the free path first — for many apps this is 20 minutes in the Supabase dashboard. If you'd rather not touch SQL, the Fix — a flat $499, code-level pass included — has a senior dev write and test the policies, rotate anything exposed, and re-scan to show a before/after. Either way, finish by running the free scan again to confirm the anonymous request now returns nothing.