Updated June 2026
Supabase RLS Hardening for Lovable and Bolt
The most common critical flaw in Lovable and Bolt apps is Row-Level Security left disabled, so any user can read or modify everyone else’s data. Fix it by enabling RLS on every table that holds user data, writing a policy per role, and never exposing the service_role key in client code.
Lovable and Bolt build their backends on Supabase, which is Postgres with an auto-generated REST API. That API is reachable straight from the browser using the public anon key, so the only thing between a stranger and your database is Row-Level Security (RLS). RLS off means the table is wide open. Generated apps demo perfectly because the happy path works, then they leak every user’s records the second someone opens the network tab. This is the exact hardening pass we run before any Supabase-backed AI app goes live.
Why RLS is the #1 gap in vibe-coded apps
Tables you create through the Supabase API ship with RLS disabled by default, and AI builders rarely turn it on or write correct policies. So you get a database anyone can query with the anon key that’s already sitting in the client bundle.
This is not theoretical. CVE-2025-48757 is a critical (CVSS 9.3) Incorrect Authorization flaw in Lovable-generated sites: insufficient or missing RLS policies let remote, unauthenticated attackers read from and write to arbitrary database tables. Security researcher Matt Palmer found 303 vulnerable endpoints across more than 170 projects, exposing names, emails, phone numbers, and payment details. No special credentials needed, because the public anon key embedded in the client let attackers query the database directly. Lovable disputes the CVE on the grounds that customers own their app data. The takeaway holds either way: ship a Supabase app and RLS is your problem, and on a vibe-coded app it’s almost certainly the first thing to check.
anon key vs service_role key: the rule that matters
Supabase hands you two API keys. The difference between them is the entire security model.
- The anon key is public by design. It’s meant to live in client code, and it respects RLS. Every request it makes gets filtered by your policies, so it’s only as safe as those policies are.
- The service_role key bypasses RLS entirely. It’s a master key that reads and writes any row in any table, ignoring every policy you wrote.
The rule: the service_role key must never appear in client code, the browser bundle, a frontend environment variable, or git history. It belongs only in trusted server-side contexts, like an edge function or a backend you control. If a tool prefixed it with VITE_ or NEXT_PUBLIC_, it’s already shipped to every visitor and you need to rotate it now. For the wider pattern, see our guide on exposed API keys in AI-built apps.
How to enable and test RLS on every table
Go through every table that holds user data. The procedure is short. The testing step is the one people skip.
- List your tables. In the Supabase dashboard, open the Table Editor or the SQL editor and find every table that stores user or business data.
- Enable RLS on each one. With RLS on and no policy, the table denies all access by default. That’s the safe place to start.
- Write a policy per role and action. Decide who can
select,insert,update, anddelete, then write an explicit policy for each. Tie ownership toauth.uid(). - Test as a real user. Sign in as user A, then try to read and modify user B’s rows with the anon key. The attempt should fail. Run it again for an unauthenticated request.
- Re-check after every schema change. New tables ship with RLS off, so any table an AI tool adds later is a fresh hole.
Here’s a simple policy that lets users see and change only their own rows on a profiles table:
-- 1. Turn on Row-Level Security
alter table profiles enable row level security;
-- 2. Users can read only their own row
create policy "Users read own profile"
on profiles for select
to authenticated
using ( auth.uid() = user_id );
-- 3. Users can update only their own row
create policy "Users update own profile"
on profiles for update
to authenticated
using ( auth.uid() = user_id )
with check ( auth.uid() = user_id );
The using clause filters which rows a query can see. The with check clause stops a user from writing a row that would belong to someone else. You need both. For the broader review process around this, see how to audit AI-generated code for security.
A Supabase security checklist
Run this before launch, and again after any AI tool touches the schema.
| Check | Why it matters |
|---|---|
| RLS enabled on every table with user data | Off by default; the single most common critical gap |
| An explicit policy per role and action (select/insert/update/delete) | RLS on with no policy denies all; missing policies break the app or get loosened unsafely |
Ownership checks use auth.uid(), not client-supplied IDs | Trusting an ID from the request lets users impersonate others |
| service_role key absent from all client code and git history | It bypasses RLS entirely; in the browser it is game over |
| Tested cross-user: user A cannot read or write user B’s rows | The only proof your policies actually work |
| Views and functions reviewed for RLS leakage | security definer functions and exposed views can bypass policies |
Want us to run this audit for you?
We do a free 15-minute build audit: you show us your AI-built app, we tell you the specific security and production gaps and what it takes to fix them. No obligation.
FAQ
Do Lovable and Bolt enable Row-Level Security automatically?
No. Supabase tables ship with RLS disabled by default, and AI builders rarely turn it on or write correct policies. That's why missing RLS is the most common critical flaw in these apps. Enabling RLS and writing a policy on every table that holds user data is on you.
Is it safe to put the Supabase anon key in client code?
Yes. The anon key is public by design and is meant to live in the client. It respects RLS, so it's only as safe as your policies. The service_role key is the opposite: it bypasses RLS and must never appear in client code, frontend environment variables, or git history.
What was CVE-2025-48757?
A critical (CVSS 9.3) vulnerability in Lovable-generated sites where insufficient or missing Row-Level Security let unauthenticated attackers read and write arbitrary database tables. A researcher found 303 vulnerable endpoints across more than 170 projects. Lovable disputes it, arguing customers own their data, but RLS is still your responsibility.
How do I test that my RLS policies actually work?
Sign in as one user and, using the anon key, try to read and modify another user's rows. The attempt should fail. Run the same test as an unauthenticated request. Tie ownership to auth.uid() rather than any ID the client supplies, and re-test after every schema change.