← Learn

Supabase row-level security, explained for founders

6 min read · 19 Oct 2026

Supabase is a Postgres database with an API in front of it. That is the whole point and the whole risk: the API is reachable from the browser, with the same key every visitor gets, and the only thing deciding who can read which row is a set of rules called row-level security policies.

AI builders like Lovable, Bolt and Base44 use Supabase heavily because it lets an app talk to its database without a backend. When the builder creates a table, RLS is often enabled with no policies, or disabled, or given a policy that says "allow everything" so the demo works. The demo works. So does everyone else's access to your data.

What a policy says

A policy is one sentence attached to one table, for one action. In plain language:

Without the first sentence, every signed-in user can read every project. Without the second, a user can create rows that belong to someone else. Without the third, a curious visitor can empty the table.

The condition in each sentence uses auth.uid(), which is Supabase's way of saying "the id of whoever is making this request". A policy that does not mention auth.uid() or something equivalent is usually a policy that lets everyone in.

The three ways apps leak

RLS is off. The table was created and the switch was never flipped. Everything is readable with the public key.

RLS is on with no policies. Counter-intuitively this is safe: no policy means no access. The app then breaks, and the usual fix is the third case.

RLS is on with a permissive policy. A policy whose condition is true, added so the screen would load. This is the most common state of an AI-built app, because it is the one that makes the error go away.

There is a fourth leak that is not RLS at all: the service_role key. It bypasses every policy. It belongs on a server and nowhere else. If it is in your front-end code, nothing in this article matters until it is rotated.

The two-user test

You do not need to read SQL to know whether you are exposed. Create two test accounts. Sign in as the first and create some data. Then sign in as the second and, using the browser's developer tools, call the same API the app calls, asking for all rows of that table. If the first user's data comes back, the policy is wrong.

Do this for every table that holds something a customer would not want another customer to see: profiles, projects, messages, orders, files.

What good looks like

Who should do this

Writing policies is an hour's work for someone who has done it before and a weekend of confusion for someone who has not. If you are about to put real users on an app built this way, have the policies read before launch, not after the first support email that says "I can see someone else's invoices". The Launch Readiness Check covers exactly this, with the verdict in writing.

Something here applies to you?