- title
- When Does a Vibe-Coded App Need RLS?
- author
- Graham Morley
- date
- 2026-09-29
- category
- Compliance & Security
- tags
- Supabase
- Vibe coding
- Architecture
- Startups
- reading_time
- 7 min read
When Does a Vibe-Coded App Need RLS?
What Row Level Security does, which vibe-coded apps need it, how apps built by AI agents end up without it, and how to check the RLS you have.
In September 2026 we read nine checklists on securing apps built with Lovable or another coding agent. Six of them mention Row Level Security, but only one says which apps need it. The answer depends on whether your database is queried by the browser or by a server you run.
What Row Level Security does
Row Level Security (RLS) is a Postgres feature that lets the database decide which rows each user can see and change. With it set up, a user who asks for every order gets back only their own, even when the request comes straight from their browser. You write the rules as policies, such as "a user can read an order if its user_id matches theirs", and Postgres checks every query on that table against them.
What problems does RLS solve?
RLS stops one user from reading or changing another user's data. Without it, a logged-in user can change an ID in a request and get back someone else's orders, messages, or account details. OWASP lists this under Broken Access Control, and one of its common examples is "Permitting viewing or editing someone else's account, by providing its unique identifier (insecure direct object references)".
It also limits what people who aren't logged in can do. When the browser talks to the database, the key it uses is public, so anyone can copy it from the page and send their own queries. With RLS on and the right policies, those queries only return what a logged-out visitor is allowed to see.
And it catches mistakes in your own queries. The database applies the policy to every query a signed-in user makes on the table, so a query that forgot to filter by user still only returns that user's rows. The exceptions are the parts of an app that skip the policies, covered below under how to check the RLS you have.
Is your app structured in a way that needs this?
RLS matters most when the browser queries the database directly, with no server of your own in between. Supabase's Data API works this way. It's PostgREST, which Supabase's architecture page describes as "a standalone web server that turns your Postgres database directly into a RESTful API". The browser calls it with a publishable key, which older docs call the anon key. Supabase's API keys page calls that key "Safe to expose online" and explains why RLS matters: "Anyone can read it, so it only reaches what Row Level Security allows." If RLS is off, the only limit is the table's grants, the basic permissions that say which database roles can read or write it at all. Supabase's RLS page says that on existing projects, a new table in public "starts with every privilege already granted" to the anon role, which is the role the public key uses, though not every project grants these automatically. On a project that does grant them, anyone with the key can read and change a new table until RLS is turned on. With no code of your own between the browser and the table, RLS policies are the only thing keeping one user out of another user's rows. The same page says to "Enable RLS on every table in an exposed schema."
To check which kind of app you have, open your browser's developer tools, go to the Network tab, and use the app while logged in. Requests to <project>.supabase.co/rest/v1/ mean the browser is querying the database, and this post applies to you. If you only see requests to your own domain, at paths like /api/..., the browser is talking to a server you run. If you see both, some parts of your app query the database directly, and the tables behind them need RLS.
If your app has its own server between the browser and the database, that server has to keep users apart. We cover that kind of app in Securing a Vibe-Coded App.
How AI-built apps end up without it
Supabase's 2025 security retrospective says tables created in its dashboard have RLS turned on by default. A coding agent that changes your database writes SQL to do it, and a table created with SQL starts with RLS off unless the SQL turns it on. For those, Supabase suggests event triggers for "external tools or migrations that don't enable RLS". In April 2026 Supabase wrote that it "kept seeing agents skip RLS policies on exposed schemas".
One documented case is CVE-2025-48757, a publicly recorded vulnerability in which apps generated by Lovable had inadequate RLS policies, so anyone could read or write their database tables without logging in. Lovable disputes the record, saying each customer is responsible for protecting their own app's data. Matt Palmer works in developer relations at Replit, which competes with Lovable. In March 2025 he scanned the home pages of 1,645 Lovable projects and found 303 Supabase endpoints across 170 of them (about 10.3 percent) with what his statement calls "inadequate RLS settings". His advisory says Lovable "creates frontend applications that make direct REST API calls against the database from the client, using a public and unprivileged anon key, while relying exclusively on RLS".
In June 2026 Symbiotic Security, which sells a code security product, published a scan of 1,072 apps that use Supabase. It found them by looking for a supabase.co address in their page code, and they were built with Lovable, v0, Bolt.new, Replit, and Windsurf. In 39 of them, anyone with the public key could read whole tables, and in 172, anyone could delete records without logging in. Lovable's built-in backend is enabled by default and "utilizes Supabase's open-source foundation", so the tables in a new Lovable app need RLS too.
How to check the RLS you have
Turning RLS on is the first step. A policy can still be written so it lets everyone through, and some parts of a Supabase app skip the policies entirely.
Start with two tests from outside the app. First, log out, copy the key and the project URL from the page source, and request /rest/v1/<table>?select=* with the key in the apikey header. If rows come back, a logged-out visitor can read that table. Second, create two accounts, log in as the second, and replay one of its requests from the Network tab with the first account's ID in place of its own. If the first account's data comes back, the policy on that table doesn't limit users to their own rows.
Then look for the parts of the app that skip the policies:
Edge Functions that use the secret key. Supabase's API keys page says "Policies never apply to a secret key, because
service_rolehas theBYPASSRLSattribute."Postgres functions marked
SECURITY DEFINER, which run with their owner's permissions.Views created without
security_invoker = true, which Supabase's April 2026 post says "silently bypasses RLS".
A passing security scan doesn't prove your policies are right. Palmer's 2025 statement said Lovable's scanner "merely checks for the existence of any RLS policy, not its correctness or alignment with application logic".
Summary
If the browser queries your database directly, RLS is your access control, and every exposed table needs it, with policies that match what each user is allowed to see. Tables created with SQL start with it off, so check each one, run the two tests above, and look for functions and views that skip the policies. If your app has its own server in between, that server has to keep users apart, and Securing a Vibe-Coded App covers how to check it.
Not sure which kind of app you have? We take on apps built by coding agents and get them ready for real users, starting with a review of the codebase, the infrastructure, and how the app is deployed.
About the author
Graham Morley, Founder, Morley Media Group
Graham has been shipping production software since 2011, including SOC 2 and ISO 27001 certified platforms, DeFi protocols managing millions, and AI products that raised venture funding. He builds, advises, and leads engineering for companies at every stage, from a clean website to a complex AI platform.