· via dev.to (home feed)
Eight data-leak patterns in Lovable-built Supabase apps and the SQL checks that catch them
A dev.to post documents eight recurring RLS failures in apps generated with Lovable and similar tools on Supabase, each paired with a quick SQL check runnable from the SQL editor.

A post on dev.to catalogues eight recurring ways that applications built with AI tools such as Lovable and Bolt on top of Supabase end up exposing their users' data, and pairs each pattern with a short SQL check that can be run from the Supabase SQL editor in a couple of minutes. The failure modes are ordered roughly by how badly they end.
A documented problem, not a hypothetical
The author anchors the piece in CVE-2025-48757, citing reporting by Matt Palmer from March 2025: more than 170 Lovable-generated applications and 303 vulnerable endpoints exposed emails, phone numbers, payment and subscription data and third-party API keys, in some cases with write access, meaning payment records could be altered rather than merely read. Lovable's response, shipped in version 2.0, was a built-in security scanner. According to the post, that scanner verifies only that RLS is switched on — it says nothing about whether the policies restrict anyone — and every pattern described keeps RLS enabled and still passes the scan.
The author is explicit that this is not a dig at the tools themselves. RLS is the piece of a Supabase app a prompt cannot fully specify: a request for an app where each company sees only its own projects gives the generator no reliable way to locate the tenant boundary, so those policies have to be written deliberately, table by table.
User-writable roles and tables without RLS
The pattern the author rates worst, and sees often precisely because it is the easiest route for a generator, is storing roles or organisation membership in user_metadata. That object is writable by the user, so any logged-in person can open the browser console, call auth.updateUser to set role to admin, and become whatever they claimed. Policies that trust that field enforce nothing more than the user's own claim about themselves. The recommended fix is a dedicated membership table keyed by auth.uid(), with a policy users cannot write from the client, or app_metadata, which clients cannot modify.
The second failure is blunter. Every table in the public schema is reachable through Supabase's REST API, and where RLS is not enabled the anon key — which ships inside the frontend bundle where anyone can read it — returns the entire table. The Supabase dashboard does warn about this, the post notes, but the warning is easy to click past on the way to a release.
Policies that exist but say yes
These are the failures a scanner cannot see, because structurally they are real policies. The blunt version tends to appear when a developer hits a permission error and asks an AI to fix it: using (true) on a select makes the error vanish and lets every logged-in user read every row. The post adds that with anonymous sign-ins enabled — a single toggle in the Auth settings — authenticated stops being a meaningful boundary at all.
The subtle version is more insidious: a policy whose EXISTS subquery only verifies that the current user belongs to some organisation, never correlating that membership to the row being filtered. Postgres asks the same question for every row, gets a yes, and hands it over. Anyone can sign up, create a free workspace and read every customer's orders with a plain select, no exploit chain required. The fix is a single join back to the row, matching the membership's tenant_id against the table's, plus a (select auth.uid()) wrapper so the planner evaluates the function once per query instead of once per row — on large tables that is the difference between a policy that survives and one that gets deleted for being slow.
Service keys, missing checks and views
Turning RLS on without writing a policy denies everything, and the fastest unblock is often to call Supabase with the service role key, which bypasses RLS entirely. If that key is anywhere the browser can reach, every protection in the database is off for whoever finds it. The author recommends grepping the built bundle for service_role, decoding any JWT found — the payload is base64, not encrypted — and checking git history, since a key that was ever committed should be rotated no matter how quickly it was removed; automated scrapers watch public repositories around the clock.
Elsewhere on the list: INSERT and UPDATE policies without a WITH CHECK clause let users rewrite org_id and move records into or out of another customer's account, with FOR ALL policies the usual suspects. And views run with their owner's privileges — in Supabase usually postgres — so a view over a protected table returns everything regardless of the policies beneath it. The security_invoker option added in Postgres 15 addresses this and is off by default.
Why it matters
AI app builders have made working software cheap to produce, but the property all these failures share — where one customer's data ends and the next begins — is exactly what a generator cannot guess and what a structural scanner cannot verify. CVE-2025-48757 showed the exposure is real at scale, not theoretical. The post's audits are queries against pg_policies and pg_class plus a grep of the built bundle, and the practical takeaway is simple: run them before the app ships, not after someone else does.
- #supabase
- #postgres
- #row-level-security
- #security
- #ai-code-generation