Skip to content

Supabase Row Level Security, explained for founders who don't write SQL

Row Level Security (RLS) is the set of rules in your Supabase database that decides which rows each visitor can read or change. Because Supabase apps talk to the database straight from the browser using a public key, RLS is the only thing standing between that key and your data. Supabase's docs say the public key is safe to expose only with RLS enabled. Check that every table has RLS on, that each policy ties rows to the signed-in user, and then prove it with two test accounts.

By VibeGuard Team4 min read

If you built your app with Lovable, Bolt, or another AI tool that uses Supabase, there's a good chance your database is reachable directly from the browser. That's how Supabase is designed, and it's a big part of why these tools can build a working app in an afternoon.

It also means one setting carries most of your app's security. It's called Row Level Security, RLS for short, and when it's missing or wrong, anyone who opens your site can read your data. It's behind the Moltbook leak and the 170 exposed Lovable apps.

This guide explains it without SQL.

Why your database key is in the browser

A traditional web app has three layers: the page in your browser, a server in the middle, and the database behind it. The page never talks to the database. It asks the server, and the server decides what to hand back.

Supabase lets you skip the middle layer. Your page talks to the database directly, using a key that ships inside the page's code. Anyone can see it. Open your site, press F12, go to the Network tab and you'll find it in the requests to supabase.co, labelled apikey.

That's fine, and Supabase says so: the public key (called the anon or publishable key) is safe to expose as long as RLS is enabled. With the server removed from the picture, something else has to decide who gets what, and RLS is that something.

What RLS actually does

RLS is a list of rules attached to each table. Before the database returns or changes any row, it checks the rules against whoever is asking.

In plain English, good rules sound like this:

  • A user can read their own profile.
  • A user can read orders where they are the customer.
  • Anyone can read published blog posts. Nobody can read drafts.
  • Nobody can read the table of API tokens from the browser at all.

When a request arrives with the public key, the database knows who's signed in (or that nobody is) and filters every row through those rules. A user asking for "all orders" gets only their own. A stranger asking for "all users" gets nothing.

Without RLS, the database has no rules to apply, so it answers every question.

Where it goes wrong

RLS is off on a table

This is the Moltbook case. Often it's a table added later ("let's store invoices too") where the AI created the table and the feature but not the rules. Supabase now warns about this in the dashboard, and so do tools like Lovable's security scan.

The rule says yes to everyone

A policy whose condition is simply true is the same as no rule. AI tools sometimes write this when a feature isn't working and the quickest fix is to open the door.

The rule only checks that someone is logged in

This is the sneaky one. It passes every "is RLS enabled?" scan, and it still means any customer can read every other customer's rows. For a B2B app, that's one client seeing another client's data.

The secret key in the browser

This one sits outside RLS entirely. Supabase gives you a second, secret key that ignores all rules, meant for servers only. If an AI tool ever put it in your frontend code to "fix a permissions error", every rule you wrote is irrelevant. Search your code for service_role and for the SUPABASE_SERVICE_ROLE_KEY variable name. It must never appear in anything that runs in the browser.

Check yours in 15 minutes

Step 1: look (5 minutes)

In the Supabase dashboard, go through every table in the public schema. Each should say RLS is enabled. Click into the policies and read them. For each table, you should be able to say in one sentence who can read and who can change its rows. If you can't, ask your AI tool to explain the policy to you in plain English, for that specific table.

Step 2: test as a stranger (5 minutes)

Sign out of your app. Ask your coding tool: "Write me a curl command that uses my Supabase public key to select all rows from the profiles table, with no user logged in." Run it. Try your most sensitive tables. You should get an empty list or an error.

Step 3: test as another customer (5 minutes)

Create two accounts, A and B. As A, create something private. Then, signed in as B, ask your tool to fetch A's row by its ID using B's session. You should get nothing.

If any of those return data they shouldn't, don't panic and don't turn RLS off. Ask your AI tool for a policy per table that ties rows to auth.uid(), the ID of the signed-in user, and re-run the tests.

What good looks like

A healthy Supabase app has RLS on for every table, policies you can explain, no secret key anywhere near the browser, and a two-account test you re-run whenever you add a table. That last habit matters most, because new tables are where the gaps come back.

This is item one on our pre-launch security checklist. If you'd like someone to run the stranger and two-account tests against your live app for you, along with the rest of the list, that's what a VibeGuard check does.

Questions founders ask

What's the difference between the anon key and the service_role key?

The anon or publishable key is meant for the browser and is limited by your RLS policies. The service_role or secret key bypasses RLS entirely and must only ever be used on a server. If the service_role key is anywhere in your frontend code, treat it as leaked and rotate it.

I turned RLS on and my app stopped working. What now?

That's expected. With RLS on and no policies, Supabase denies everything by default. The fix is to add policies that allow exactly what each feature needs, such as 'users can read their own rows', not to turn RLS back off.

Can I ask my AI coding tool to write the policies?

Yes, and it's usually good at it when you ask specifically, table by table, and say who should be able to read, create, update and delete each row. Then test the result with two accounts. Don't accept a policy you can't explain in one sentence.

Sources

  1. Row Level Security · Supabase Docs
  2. Securing your data · Supabase Docs
  3. Statement on CVE-2025-48757 · Matt Palmer, 29 May 2025

Is your app one of them?

An independent security check of your live app, from €59, with fix prompts for the coding tool you already use. Read-only, and it never touches your code.

Check my app

Keep reading

All articles