
If your app was built with Lovable, Bolt or a similar AI builder, there is a good chance it uses Supabase for its database and login. That is a sensible choice. It also means one setting decides whether your customers' data is private or public: Row Level Security, usually shortened to RLS.
Security researchers publicly reported in 2025 that a large number of apps generated by one popular AI builder exposed user data because RLS policies were missing or too weak. The apps worked perfectly in testing, because the builder and the owner could always see everything. The problem only shows when a different user, or an attacker, asks for data that is not theirs.
This guide explains RLS in plain terms, then walks through the seven mistakes that appear most often in AI-generated Supabase projects, with the fix for each.
RLS in plain English
Supabase gives your frontend direct access to your database through an automatically generated API. The "anon" key that ships inside your website is public by design. Anyone can copy it from the browser. What protects your data is not the key, it is the rules attached to each table.
Row Level Security is that rulebook. Each rule (a policy) says which rows a given kind of user may read, insert, update or delete. A typical rule is "a signed-in user can read rows where user_id equals their own ID". If RLS is off for a table, or the policies are too generous, the rulebook does not apply and the API will answer anyone.
Mistake 1: RLS was never turned on
Tables created through SQL or migration files are not protected until you enable RLS on them. AI tools often create tables, build the screens, and move on. Everything works because there are no restrictions at all.
To check, run this in the Supabase SQL editor and look for any table in the public schema where rowsecurity is false:
select schemaname, tablename, rowsecurity
from pg_tables
where schemaname = 'public';
Enable it with one line per table, then add policies. Enabling RLS with no policies blocks everything, which is the safe default to build from:
alter table public.invoices enable row level security;
The Supabase dashboard also has a Security Advisor that flags tables without RLS. Run it before every launch.
Mistake 2: policies that allow everything
When an AI tool hits a "permission denied" error, one common shortcut is a policy such as using (true). It makes the error disappear and the data public at the same time.
-- Looks harmless, exposes every row to every user:
create policy "Allow read" on public.invoices
for select using (true);
Replace it with a rule tied to the signed-in user:
create policy "Users can read their own invoices"
on public.invoices for select
to authenticated
using ( (select auth.uid()) = user_id );
Genuinely public data, such as a product catalogue, can use an open read policy. Anything personal, financial or private cannot.
Mistake 3: the service key is exposed
Supabase has two keys that matter. The anon key is meant for browsers and relies on RLS. The service role key bypasses RLS completely. If it appears in your frontend code, in a variable that gets bundled into the website, or in a public repository, every policy you wrote is irrelevant.
Search your built JavaScript and your Git history for it. If it was ever exposed, rotate it in the Supabase dashboard immediately. Service keys belong only in server-side code, such as an edge function or backend, never in anything the browser downloads.
Mistake 4: roles stored where users can edit them
A frequent pattern is to mark admins with a field such as role: "admin" inside the user's metadata, then check it in a policy or in the interface. In Supabase, the user_metadata area can be changed by the signed-in user. A user can simply give themselves the admin flag.
Keep authorization data in places users cannot write: a dedicated roles table that is itself protected by RLS, or the server-controlled app_metadata. Then base your policies on that.
Mistake 5: insert and update rules without a "with check"
A policy can control which existing rows you may touch (using) and which values you may write (with check). If the second part is missing, a user can insert rows that belong to someone else, or update their own row to hand it to another account.
create policy "Users can insert their own invoices"
on public.invoices for insert
to authenticated
with check ( (select auth.uid()) = user_id );
create policy "Users can update their own invoices"
on public.invoices for update
to authenticated
using ( (select auth.uid()) = user_id )
with check ( (select auth.uid()) = user_id );
Mistake 6: views and functions that skip the rules
A database view runs with the privileges of its owner by default, which can bypass the RLS on the tables underneath. In Postgres 15 and later you can make a view respect the caller's permissions with security_invoker = true. Likewise, a function marked security definer and exposed to the API runs with elevated rights, so it must check who is calling and fix its own search_path.
AI-generated "helper" views and functions for dashboards are a common source of this problem. Review each one and ask: whose permissions does this run with?
Mistake 7: storage buckets and multi-tenant gaps
Files get their own rules, separate from table rules. A public bucket serves files to anyone with the link, which is right for logos and wrong for contracts. For private files, organise uploads by user or tenant and write storage policies to match, for example by requiring that the first folder in the path equals the user's ID:
create policy "Users can read their own files"
on storage.objects for select
to authenticated
using (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid()::text)
);
For apps with teams or organisations, every policy should scope to the tenant through a membership table, not only to the individual user. That is also where AI-written policies most often become inconsistent from table to table.
How to test your own app in 15 minutes
- Anonymous test. Call your table endpoints using only the public anon key and no login. Private tables should return nothing or an error.
- Two-account test. Create two users with separate data. As user B, try to read, edit and delete user A's records by changing IDs in requests.
- Privilege test. As a normal user, try the admin features directly, and try to change your own role.
- Run the Security Advisor and fix every warning about RLS.
- Keep the tests. Supabase supports automated database tests, so a future change cannot quietly reopen a hole.
If you have already launched
Enable RLS and fix the policies first, then rotate any exposed keys. Check your logs for unusual access. If personal data of real users may have been exposed, you may have legal duties to notify them depending on where they live, so speak to a lawyer. Fixing the access rules is usually quick. Working out what happened and what to disclose takes longer, which is another reason to find these problems before launch.
Not sure where your app stands? Our AI app technical audit reviews your policies, keys and storage and gives you a ranked list of fixes. If you already know what is broken, AI app repair and production launch covers the fixes and a controlled release. You can also start with the wider production readiness checklist.
Frequently asked questions
What is Supabase Row Level Security?
RLS is a Postgres feature that Supabase uses to decide which rows each user may read, insert, update or delete. Policies attached to each table act as the rulebook. Without them, anyone with your public API key can reach the table through the auto-generated API.
Is the Supabase anon key safe to expose in my frontend?
Yes, it is designed to be public, but only if Row Level Security is enabled with correct policies on every exposed table. The service role key is different: it bypasses RLS and must never reach the browser.
How can I check whether my Lovable or Supabase app is leaking data?
Run the Security Advisor in the Supabase dashboard, query pg_tables to find public tables without RLS, call your table endpoints with only the anon key, and try to read another user's records with a second test account.
Can the AI builder fix Row Level Security for me?
It can draft policies, but it cannot reliably verify them against your data model, roles and tenants. Always test with at least two accounts, and review any policy whose condition is simply true or that lacks a with check clause.
How we can help
- AI App Technical AuditFixed-scope review of apps built with Lovable, Cursor, Bolt, Replit or v0 — auth, Supabase RLS, Stripe, secrets and deployment — with a prioritized fix plan.
- AI App Repair & Production LaunchFix the login, Supabase permission, Stripe, API and deployment issues blocking your AI-built app, then ship a controlled production release.
- Cybersecurity & AI Security ServicesPenetration testing, SOC monitoring, SOC 2 / ISO 27001 / PCI DSS / HIPAA readiness, and emerging-area work in LLM red teaming and AI agent security.
Talk to an engineer about your project
Tell us what you are building. We reply within one business day with a candid view on scope, approach and effort.
Book a free strategy callWritten by the UnlockLive IT engineering team. UnlockLive IT Limited works with clients through its Toronto headquarters and delivers engineering from its Dhaka delivery centre. About us