Skip to content

Is your Lovable app's database readable by anyone?

Supabase creates every table with Row Level Security disabled, and the anon key in your frontend is handed to every visitor. If nothing turned RLS on, that key reads your tables. Since February 2026 Lovable checks for this before every publish, which closes the common case. This page is about the cases it leaves open, and how to find out in two minutes which one you are in.

Loïc GuillebeauLoïc Guillebeau7 August 2026 · Last reviewed 9 August 2026

We audited ten applications built with AI over the last three months. The most common critical finding, by a distance, was the same one: an endpoint or a table that returns data without ever checking who is asking. It is worth being precise about who is still exposed to that in 2026, because the honest answer is no longer everybody.

What Lovable already does

Lovable 2.0 runs a basic security scan automatically before every publish. It takes ten to fifteen seconds, and by their own description it covers database misconfigurations, missing RLS policies and authorization gaps. Auto-fix, which is opt-in, resolves the straightforward low-risk findings as non-breaking changes. Dependencies are re-checked on every edit, and a deep scan, described as a full AI review of the codebase, runs on demand in two to four minutes.

If you built your app in Lovable this year and you publish regularly, that pass has almost certainly already looked at the thing this page is about, and it is free with the product. Start there. The rest of this page is for the three situations where it has not.

The three apps the scan has not seen

Lovable states the boundary of the automatic pass themselves, and it is a fair one: it does not analyse your application code for logic flaws or vulnerabilities unique to how your app is built. Around that sentence sit three groups of people.

  • You built the app before February 2026 and have not published since. The scan runs at publish. An app that has been quietly serving users for a year has never been through it, and the tables it created still have whatever settings they were created with.
  • Part of your system was not built in Lovable. The admin panel a contractor added, the cron job running on a server somewhere, the second repository nobody mentions in meetings. None of it is in scope for a check that runs inside the builder.
  • The hole is in your logic rather than your configuration. This is the one that does not go away, and the rest of this page is mostly about it.

Why a policy check cannot be the whole answer

An AI builder is very good at going from a sentence to a working schema with a frontend wired to it. To do that it needs a key, and the key it puts in the browser is the anon key. Supabase's own model expects that: the anon key is meant to be public, because Row Level Security is what stands between it and your data. A scan can check whether that wall is standing, and Lovable's does.

What no scan can check is whether the door in the wall leads where you think. Consider a refund endpoint that reads an order id from the request body, looks it up, and refunds it. RLS is on. The policy is correct. The query is authorised, because the caller is a legitimately authenticated user. They are simply refunding somebody else's order. Nothing about that is a misconfiguration, and it is only wrong if you know what the app is for.

That is the shape of most of what we find on the paths that matter, and it is why the check below is worth two minutes even if a scan has already told you that you are fine. Grace vs Lovable sets out where one stops and the other starts.

For what this looks like when it goes wrong at scale: CVE-2025-48757 covered exactly this class of failure across more than 170 Lovable applications, where missing or insufficient RLS policies allowed access to other users' data. It dates from 2025, before the automatic scan existed, so read it as a description of the failure mode rather than as a claim about apps published today.

Check it yourself, in two minutes

Open the SQL editor in your Supabase project and run this. It lists every table in your public schema and whether RLS is on.

select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

Every row that comes back with rowsecurity = false is a table that any visitor holding your anon key can read. If you would rather see it from the outside than take anyone's word for it, open your deployed site, find the anon key in the network tab (it is in the request headers, it is meant to be there), and query a table you believe is private.

There is a second check worth doing in the same sitting, because the two failures travel together: search your frontend bundle for a key beginning with the service_role prefix. That key bypasses RLS entirely. It belongs on a server and nowhere else, and it reaches browsers more often than anyone would like. Here is how to read the whole bundle, and what to do first if something comes back.

And a third, which is the logic one and takes longer: pick the three routes that move money or expose personal data, and for each one ask what stops the caller passing an id that is not theirs. If the answer is that the frontend only ever sends their own, that is not an answer.

The fix, and the part everyone gets wrong

Turning RLS on is one statement per table.

alter table public.orders enable row level security;

create policy "own rows" on public.orders
  for select using ((select auth.uid()) = user_id);

The part that goes wrong is what happens next. Enabling RLS without a matching policy denies everything, so features start failing in ways that look unrelated: a list that renders empty, a dashboard that shows zeros, a webhook that silently stops writing. Enable it across a live app on a Friday afternoon and you will spend the weekend discovering which flows were depending on the hole.

  • Enable RLS one table at a time, starting with the ones holding personal or payment data.
  • Write the read policy and the write policy together. A table that can be read correctly and written by anyone is still an open table.
  • Wrap auth.uid() in a select, as above. Called directly it is re-evaluated per row, and it is a common cause of a policy that is correct and makes the query crawl.
  • Exercise the real flows afterwards: signup, checkout, whatever the app exists to do. Not the happy path in isolation, the whole sequence.
  • Check anything that writes from a server or a cron. Those often ran as a privileged client precisely because RLS was off.

If you cannot read the code

Plenty of people who built a real business on a Lovable app cannot run the query above, and that is a legitimate position to be in. If the app is current and everything you have is inside Lovable, run their deep scan first. It is included, it takes a few minutes, and for a lot of apps it is enough.

Go further when you are in one of the three groups near the top of this page: an app the scan has never seen, a system that is only partly Lovable, or logic that has grown past what any policy check understands. Then the options are a developer for a couple of hours, or something that reads the whole repository and tells you which of what it finds is on a path your users are actually on.

That second one is what Grace does. It reads the repo rather than scanning a deployed URL, ranks what it finds by impact rather than by count, and the audit is free: you get the map and the ranked risks without paying and without anything being changed. Whether you then fix it yourself, hand the list to someone, or let Grace open the pull requests is your call.

Loïc Guillebeau

Loïc Guillebeau

Founder, Grace · founder of Beyond the Brackets

Seven years running an engineering agency, ten AI-built applications audited in the last three months. Grace came out of both.

More about who is behind Grace →

Want to know what else is in there?

Grace reads the repository and comes back with the architecture map, the risks ranked by severity, and a health score. It is free, it is read-only, and it changes nothing.