If your app was built with Lovable, Bolt or a similar AI tool, there’s a good chance its data lives in Supabase. Supabase is an excellent platform. But it relies on one setting being right, and when it isn’t, anyone on the internet can read, and sometimes change, your customers’ data.
That setting is Row Level Security, usually shortened to RLS. Our vibe-coded app launch checklist covers the whole picture. This post goes deep on the one item that causes the most serious problems: what RLS is, why AI-built apps get it wrong, how to test yours, and the policy mistakes we see most often.
Why is Supabase RLS such a big deal for Lovable and Bolt apps?
Most traditional web apps put a server between the browser and the database. The server decides who can see what. Many AI-built apps skip that layer: the browser talks to Supabase directly.
To make that work, the app ships with two things anyone can see by opening their browser’s developer tools:
- Your Supabase project URL.
- A public key, called the anon key on older projects and the publishable key on newer ones.
That’s by design. Supabase describes the publishable key as safe to expose online. It’s only safe, though, because Row Level Security decides which rows each request can touch. If RLS is off, or the policies are wrong, the public key becomes a key to your whole database.
This isn’t a theoretical risk. In 2025, a security researcher scanned 1,645 apps built with Lovable and reported that 170 of them had inadequate RLS settings, exposing 303 endpoints. The issue was published as CVE-2025-48757, which describes insufficient RLS policies allowing “remote unauthenticated attackers to read or write to arbitrary database tables of generated sites”.
What is Row Level Security, in plain English?
Row Level Security is a feature of Postgres, the database Supabase runs on. It lets you write rules, called policies, that are checked for every row on every request. A policy might say: “a user can only read orders where the customer ID matches their own user ID”.
Supabase’s own guidance is clear: enable RLS on every table in an exposed schema. Without it, a table is readable and writable by any role that has access to it, which on a typical project includes anonymous visitors using your public key.
Three things to understand:
- RLS off means no row-level rules at all. Anyone with the public key can query the table.
- RLS on with no policies means nothing gets through the API. Safe, but your app won’t work, which is often why someone switched RLS off “temporarily”.
- RLS on with policies means access depends entirely on how good the policies are. This is where the subtle mistakes live.
How can you test your app’s RLS?
You don’t need to be a database expert to run a basic test, but do it against a test copy of your app if you can, and never against data you don’t own.
- Check the dashboard. In Supabase, open the Table Editor and look at each table. Any table without RLS enabled is flagged. Supabase’s Security Advisor also lists tables in exposed schemas without RLS.
- Query as an anonymous user. Using only your project URL and public key, with nobody logged in, try to read each table through the API. Unless a table is meant to be public, such as a product catalogue, you should get nothing back.
- Query as the wrong user. Log in as customer A and try to read or update customer B’s records by changing an ID in the request. If it works, your policies don’t do what you think.
- Try to write, not just read. Many apps lock down reading but forget inserting and updating. Can a logged-in user change their own role, mark an unpaid order as paid, or edit someone else’s record?
- Check storage too. Supabase Storage buckets have their own access policies. Make sure invoices, ID documents and photos aren’t sitting in a public bucket.
If any of these tests surprise you, stop and fix them before launch, or before you collect any more customer data.
What are the most common RLS policy mistakes?
Turning RLS on is step one. These are the mistakes that let data leak even when it’s switched on.
“Logged in” isn’t the same as “allowed”
A policy that lets any authenticated user read a table sounds safe. But anyone can sign up to your app and become an authenticated user. Policies almost always need to compare the row’s owner with the current user, typically using auth.uid().
Forgetting the anonymous case
When nobody is logged in, auth.uid() returns null. Supabase recommends making that explicit in policies, for example auth.uid() IS NOT NULL AND auth.uid() = user_id, so the rule’s intent is obvious and can’t be accidentally weakened later.
Locking reads but not writes
Postgres policies are set separately for select, insert, update and delete. A policy on reading doesn’t protect writing. Updates are the dangerous ones: a user editing their own profile shouldn’t be able to set is_admin = true or change a price.
Missing WITH CHECK on inserts and updates
In a policy, USING controls which existing rows a user can touch, and WITH CHECK controls what the new or changed row is allowed to look like. Without a proper check, a user might be able to create a record that belongs to someone else, or move one of their records into another customer’s account.
Trusting columns the user can change
If a policy relies on a column such as role or organisation_id, make sure users can’t edit that column themselves. Otherwise they can change the very value your security depends on.
Views and functions that bypass RLS
By default, Postgres views run with the permissions of the user who created them, which can bypass RLS. On Postgres 15 and later, Supabase notes that setting security_invoker = true makes a view respect the underlying table’s policies. Database functions marked security definer need the same care.
The secret key in the browser
Supabase’s secret key (the service_role key on older projects) bypasses RLS entirely. It must only ever live on a server you control. If it has been in your front-end code, your app bundle or your Git history, assume it’s compromised and rotate it.
Does RLS slow your app down?
It can, if policies filter on columns without indexes. Supabase recommends adding an index on every column your policies filter on, and wrapping functions like auth.uid() in a select so Postgres evaluates them once per query rather than once per row. Our guide to why apps don’t scale covers more of these performance traps.
Is the AI tool responsible for getting RLS right?
AI builders have improved, and several now warn about missing RLS or run security scans. But the responsibility for your customers’ data sits with your business, not the tool. Under Australian privacy law, if you’re covered by the Privacy Act, you’re responsible for protecting the personal information you collect, and a data breach likely to cause serious harm may need to be notified under the Notifiable Data Breaches scheme.
AI tools are brilliant at getting an app working. They’re less reliable at thinking like an attacker, and RLS policies are exactly the kind of detail that looks fine until someone goes looking.
For platform-specific checklists, see our Lovable security audit and Bolt app security audit pages.
FAQs
Is the Supabase anon key a secret?
No. The anon or publishable key is designed to be public and will always be visible in your app. Your security comes from RLS policies, not from hiding that key. The secret or service role key is different and must never be exposed.
I turned on RLS and my app stopped working. What now?
That’s expected: with RLS on and no policies, nothing gets through. The fix is to write policies for each table and operation, not to switch RLS back off. Start with the most sensitive tables, such as users, orders and payments.
Can I check RLS without a developer?
You can run the basic checks above, especially the dashboard check and the “wrong user” test. Writing correct policies, and testing every table and operation, is a job for someone experienced with Postgres.
Get your RLS checked before launch
Our AI app launch audit reviews every table, policy, storage bucket and key in your Supabase project, along with the rest of your app, and explains what we find in plain English. Audits are fixed price and quoted up front. If you’d like to talk it through first, book a free 30-minute chat with an engineer.



