Platform Teardown · Lovable

Is your Lovable app actually safe for real users?

Lovable generates React + Supabase apps fast. Here are the production gaps that appear in nearly every Lovable-built app — and how to know if yours has them.

Default stack: Supabase · Supabase Auth

Lovable is very good at the part most people find hard: turning a description into a working React front end wired to a real Postgres database. What it does not do is decide who is allowed to read which row. That decision is a Postgres policy, it has to be written per table, and nothing in the build flow stops you from shipping without it.

That is the shape of almost every Lovable production gap. The app works. The feature you asked for exists. The thing that is missing is a boundary — between one user's data and another's, between your storage bucket and the open internet, between a secret and a browser bundle. Boundaries are invisible when they are missing, which is why nobody notices until someone goes looking.

Everything below is checkable in a few minutes, mostly from the Supabase SQL editor. Run the checks in order; the first two are the ones that turn into incidents.

The part that breaks first

With RLS off, the first curious user who opens devtools can read every other user's rows. Not a hypothetical — it's the default state of most Lovable apps until someone adds the policies, and Lovable doesn't warn you it skipped them.

And then this becomes your life

So you start checking the Supabase dashboard every morning. Refreshing the logs before bed. Watching the auth table for sign-ups you didn't expect. The app was supposed to run itself — instead you've become its night-shift security guard.

You shipped a Lovable app to get something off your plate — a product that earns while you sleep. What you got is a second job you can't quit, because you're the only thing standing between your users' data and the internet.

The good news: every one of these is findable, and most are fixable fast. Here's exactly what tends to be wrong in Lovable apps.

Common gaps in Lovable apps

These aren't hypothetical — they show up in the free audits. See the full production-readiness guide →

#GapSeverityWhere to look
1Row-Level Security disabled on user tablesCriticalSupabase → Database → Tables
2Storage buckets left publicHighSupabase → Storage → Buckets
3Service role key reachable from the browserCriticalClient bundle / env config
4Auth rate limits left at their defaultsHighSupabase → Authentication → Rate Limits
5No indexes on foreign keysMediumPostgres schema

1.Row-Level Security disabled on user tables

Critical

Postgres row-level security is per-table and off until someone turns it on. Lovable creates the tables; adding the policies is a separate step that is easy to skip. Until you add them, the anon and authenticated keys that ship in your front end can read every row of every table they can reach.

How to check yours

Run this in the Supabase SQL editor. Any row that comes back with rowsecurity = false and holds user data is readable by every signed-in user of your app — and, if the table is exposed to the anon role, by anyone at all. Then run the second query: a table with RLS enabled but zero policies denies everything, which is safe but usually means a feature is quietly broken.

sql
-- 1. Which public tables have RLS off?
select tablename, rowsecurity
from pg_tables
where schemaname = 'public'
order by rowsecurity, tablename;

-- 2. Which tables have RLS on but no policies at all?
select t.tablename
from pg_tables t
left join pg_policies p
  on p.schemaname = t.schemaname and p.tablename = t.tablename
where t.schemaname = 'public' and t.rowsecurity and p.policyname is null;

2.Storage buckets left public

High

A public Supabase bucket serves every object in it to anyone who has the URL, with no auth check. Profile photos, uploaded documents, receipts, ID scans — if the upload flow works and the bucket is public, the files are on the open web the moment they are written.

How to check yours

List your buckets and their visibility. For any bucket marked public that holds user uploads, copy one object URL and open it in a private window with no session. If it renders, so will everyone else's. The fix is a private bucket plus signed URLs generated server-side with a short expiry.

sql
select id, name, public, created_at
from storage.buckets
order by public desc, name;

3.Service role key reachable from the browser

Critical

Supabase issues two keys. The anon key is meant to be public and is constrained by RLS. The service role key bypasses RLS entirely and is meant to stay on a server. Any secret referenced from a component ends up in the JavaScript bundle, where the browser — and every visitor — can read it.

How to check yours

Build the app and search the output. Both Supabase keys are JWTs and start with the same prefix, so decode any match you find and look at the role claim: anon is fine, service_role is a full database compromise. Rotate immediately if you find one, because it has been public for as long as the build has been deployed.

bash
npm run build
grep -rEo 'eyJ[A-Za-z0-9_-]{20,}' dist/ .next/ build/ 2>/dev/null | sort -u
# decode a match to read its "role" claim:
#   echo '<payload-segment>' | base64 -d

4.Auth rate limits left at their defaults

High

Supabase does apply default rate limits to auth endpoints, but they are project-wide and sized for a busy app, not for yours. Sign-up, magic-link, and password-reset endpoints at default ceilings are comfortable room for credential stuffing and for burning through your email quota via someone else's inbox.

How to check yours

Open Authentication → Rate Limits and read the current per-hour ceilings for email sends, OTP sends, token refreshes, and sign-ins. For each one, ask what a single legitimate user could plausibly need in an hour, then set the ceiling near that. Also confirm email confirmations are required — an unconfirmed-signup flow is a spam amplifier.

5.No indexes on foreign keys

Medium

Postgres indexes a primary key automatically. It does not index the other side of a foreign key. Generated schemas rarely add those, so every join and every filtered list scans the whole table — fine at 100 rows, a timeout at 100,000.

How to check yours

This query lists foreign-key columns with no index behind them. Add one for each, then re-run your slowest page query with EXPLAIN ANALYZE and confirm the Seq Scan is gone.

sql
select c.conrelid::regclass as table_name,
       a.attname                as column_name
from pg_constraint c
join unnest(c.conkey) with ordinality k(attnum, ord) on true
join pg_attribute a on a.attrelid = c.conrelid and a.attnum = k.attnum
where c.contype = 'f'
  and not exists (
    select 1 from pg_index i
    where i.indrelid = c.conrelid and a.attnum = any(i.indkey)
  );

Lovable checklist

Work through these in order before you share the URL publicly. The items marked critical are the ones that expose data to people who never had an account.

  1. 1.

    RLS enabled on every table that contains user datacritical

  2. 2.

    Every RLS-enabled table has at least one policy that actually matches your access patterncritical

  3. 3.

    Storage buckets set to private with signed URLscritical

  4. 4.

    No service role key in frontend code or the built bundlecritical

  5. 5.

    Auth rate limits tightened from their project defaultscritical

  6. 6.

    Indexes on foreign keys and frequently queried columns

  7. 7.

    Error handling for failed Supabase queries (no silent failures)

Lovable production-readiness FAQ

The questions that come up most often about Lovable apps in production.

Is my Lovable app secure by default?

No — and that is a statement about Postgres, not about Lovable. Row-level security is off per table until someone enables it and writes a policy, storage buckets are public until switched to private, and neither is part of generating a working feature. A Lovable app is secure once you have added those boundaries yourself and verified them.

How do I check if Row-Level Security is enabled on my Supabase tables?

Open the Supabase SQL editor and run: select tablename, rowsecurity from pg_tables where schemaname = 'public'; Any table with rowsecurity = false that holds user data is readable by every authenticated user of your app. Enabling RLS is only half the job — a table with RLS on and no policies denies all access, so check pg_policies too.

Can someone read other users' data in a Lovable app?

Yes, if RLS is off on the table holding it. Your front end ships a Supabase key and talks to the database directly, so filtering rows in React does nothing — anyone can open devtools, take the key and the project URL, and query the table without your UI in the way. The row filter has to live in the database.

Are Supabase storage buckets public by default?

Buckets are created with a visibility setting, and a public bucket serves every object in it to anyone with the URL, with no auth check at all. Run select id, name, public from storage.buckets; and treat any public bucket holding user uploads as already on the open web. Private buckets plus short-lived signed URLs are the fix.

What is the difference between the anon key and the service role key?

The anon key is designed to be public and is constrained by your RLS policies. The service role key bypasses RLS entirely and is designed to live on a server. If a service role key ever reaches the browser bundle, it grants full read and write access to your database to every visitor — rotate it immediately.

What should I fix first?

RLS on the tables holding user data, then storage bucket visibility, then any secret that reached the client. Those three are the ones that expose data to people who never had an account. Indexes and error handling matter, but they cost you performance and support tickets rather than a breach.

Related

Find out before your users do.

Get the free audit. Three quick fields, a written report personally reviewed by Shane Jordan — not a scanner. You'll know exactly what's wrong in your Lovable app and what to fix first.

Request Free Audit

I take a limited number of audits at a time · priority review available

Operational IntelligenceOur newsletter on operational AI, in your inbox.
Subscribe free →