Skip to content
← Back to the blog
appsinfrastructure

Supabase Row Level Security for Lovable and Bolt apps

How to tell if your Lovable or Bolt app's Supabase data is open to anyone, what Row Level Security does, and how to turn it on safely without breaking the app.

By Devonauts Team7 min read
Leer en español →
An engineer holds a laptop while standing beside server racks

Row Level Security (RLS) is the set of rules inside your Supabase database that decides which rows each person can read, add, change or delete. If a table that holds user data has RLS turned off, or has a policy that allows everyone, anyone who opens your app can usually read that table directly. You can check this in a few minutes with the Supabase Security Advisor and a test using two accounts, and you can fix it table by table without taking the app down.

This guide is for people who built their app with Lovable or Bolt on top of Supabase and want to know whether their data is safe.

Why does RLS matter so much in Lovable and Bolt apps?

Apps built this way usually talk to the database straight from the browser. To do that, the app ships with your project URL and a public key (called the anon key, or the publishable key in newer projects). That key is meant to be public. Anyone can find it in a few seconds with the browser's developer tools.

That is fine, and it is how Supabase is designed to work, as long as RLS is doing its job. With the public key alone, a visitor can send requests to your database without using your app's screens at all. The only thing standing between them and your data is the policies on each table.

Supabase's own documentation puts it plainly: a table in an exposed schema without RLS is readable and writable by any role that has access to it. In a typical project, that includes visitors who aren't signed in.

Which backend is your app using?

Before you check anything, find out where your database lives.

  • Lovable with your own Supabase project. The project appears in your Supabase dashboard, and you can use every check in this guide there.
  • Lovable Cloud. Lovable manages the Supabase instance for you. It doesn't show up in your Supabase dashboard, so you work through Lovable's Cloud view and its security scan. Lovable runs a security check before publishing. Read every warning it gives you before you click past it.
  • Bolt with Supabase. Everything here applies directly.
  • Bolt Database. Bolt can also host the database for you. Check Bolt's documentation for how it handles access rules. The two-account test below works no matter which backend you use.

How can you tell if your data is open?

Run these four checks in order. The first two take a couple of minutes.

1. Open the Security Advisor

In the Supabase dashboard, open Advisors and then Security Advisor. It lists problems such as tables in the public schema with RLS disabled. Treat any error there about RLS as urgent.

The Table Editor also marks tables that have RLS turned off, so scroll through your tables and look for that label.

2. List your tables and policies

If you're comfortable pasting a query into the SQL Editor, this one lists the tables in the public schema that have RLS turned off:

select tablename
from pg_tables
where schemaname = 'public' and rowsecurity = false;

And this one shows every policy and what it allows:

select tablename, policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;

These only read information. They don't change anything.

3. Look for policies that allow everything

In the second list, watch for two warning signs:

  • A qual or with_check of true. That means "everyone", which is fine for a public list of blog posts and a serious problem for orders, messages or profiles.
  • Tables that have a policy for reading (SELECT) but nothing for INSERT, UPDATE or DELETE, or the other way around. Each action needs its own thought.

AI tools sometimes write an "allow all" policy to make an error go away. The app starts working again and the data is left open.

4. Test with two accounts

This is the test that matters most, because it checks what really happens.

  1. Create two accounts in your app, user A and user B.
  2. As user A, create something private: a note, an order, a profile detail.
  3. Sign out, sign in as user B, and try to reach A's item. Change an ID in the address bar, or open the page where A's data would appear.
  4. If B can see or edit anything of A's, that table needs fixing.

Also try the app signed out in a private window. Pages that should require login shouldn't show any real data, even for a second.

How do you fix RLS without breaking your app?

Turning RLS on is one line. Doing it safely takes a bit more care, because a table with RLS on and no policies returns nothing at all. Your app won't show an error; lists will simply come back empty, and it will look broken.

Follow this order:

  1. Take a backup first. Check that your plan's backups are running, or make your own copy before you change anything.
  2. Work one table at a time. Start with the most sensitive one (users, orders, payments, messages).
  3. Write a policy for each action you need. For a table where each row belongs to one user and has a user_id column, the usual pattern looks like this:
alter table public.notes enable row level security;

create policy "Read own notes" on public.notes
  for select to authenticated
  using ( (select auth.uid()) = user_id );

create policy "Create own notes" on public.notes
  for insert to authenticated
  with check ( (select auth.uid()) = user_id );

create policy "Edit own notes" on public.notes
  for update to authenticated
  using ( (select auth.uid()) = user_id )
  with check ( (select auth.uid()) = user_id );

create policy "Delete own notes" on public.notes
  for delete to authenticated
  using ( (select auth.uid()) = user_id );

using decides which existing rows someone can touch. with check decides what a new or changed row is allowed to look like, which stops a user from creating rows in someone else's name or handing a row to another user.

  1. Run the two-account test again after each table.
  2. Add an index on user_id for large tables. Policies run on every query, and Supabase recommends indexing the columns they filter on.

Your app might be more complex than this, with teams, shared projects or admin roles. In that case the policies need to check membership in a team table, and that's where a second pair of eyes helps.

What else can leave data exposed?

RLS on your tables is the main thing, but check these too:

  • The secret key in the browser. The service_role key (or the newer secret key) skips RLS entirely. It must only live on the server, in Edge Functions or your backend. If it ever appeared in your front-end code, rotate it in the dashboard. Our security checklist for AI-built apps shows how to search for it.
  • Views. A view usually runs with its creator's permissions, which can skip the RLS of the tables underneath. On recent Postgres versions, creating it with security_invoker = true makes it respect those rules.
  • Storage buckets. Files have their own policies. A bucket marked public can be read by anyone with the link.
  • Edge Functions that use the secret key. They need to check who is calling them, because RLS won't do it for them.
  • Admin checks in the screen only. Hiding an admin button doesn't protect anything if the database accepts the request.

How should you ask the AI to fix it?

If you'd like Lovable or Bolt to make the change, be specific and ask for an explanation first:

Table orders has RLS disabled. Enable RLS and add policies so each signed-in user can only select, insert, update and delete rows where user_id equals their own id. Don't use true in any policy and don't change any other table. Explain the policies before applying them.

Then run the two-account test yourself. Don't take the AI's word for it, and if a fix makes the error go away by allowing everything, undo it.

Quick answers

Is the public key in my code a leak? No. The anon or publishable key is designed to be public. The secret or service_role key is the one that must never reach the browser.

I turned RLS on and my app shows nothing. What happened? RLS is working, and no policy allows reading yet. Add a select policy for the right users.

Do public tables need RLS too? Yes. Turn it on and add a read policy for everyone, so the table is readable but nobody can write to it from the browser.

Can I check without touching the database? Yes. The Security Advisor and the two-account test don't change anything.

Want someone to check it for you?

Start with the free App Health Check, which looks at what your app exposes from the outside. For a full review of your tables, policies and functions, our App Rescue Audit is $299 with a written report in 2 business days, and it's fully refunded if it finds no medium or high severity issue. See how we fix Lovable apps or Bolt apps, check our pricing, or get in touch.

Still stuck with your app?

Run the free health check on your app's URL, or get the $299 App Rescue Audit: a senior engineer reviews the code and sends you a written report in 2 business days.