← Back to blog
Findings Mar 29, 2026 · 3 min read

Top 5 security issues we found on Lovable apps

We scanned ~50 published Lovable apps. About 1 in 5 of the Supabase-backed ones had at least one table readable by anyone. Here's the pattern.

Over the past two weeks we ran Security Scanner against ~50 apps published on Lovable. About 28 of them used Supabase as the backend (we detected the Supabase URL + anon key in the JS bundle). Of those Supabase-backed apps, roughly 1 in 5 had at least one Supabase table readable by anyone holding the anon key.

Here are the top 5 issue patterns, ranked by how often they showed up.

1. Row Level Security disabled on app-specific tables (~18% of Supabase-backed apps)

By far the most common critical finding. RLS is per-table in Postgres, and if a table is created via plain CREATE TABLE in the Supabase SQL Editor (which is what most AI assistants do when they scaffold a new feature), RLS is off by default. The dashboard's Table Editor flips it on; SQL doesn't.

Tables we've found anon-readable across this cohort include: client_emails (with a literal password column), booking_requests (customer emails + phone numbers), client_accounts (a credential vault — service names, emails, passwords for things like Namecheap), chat_channels, chat_messages, subscriptions, profiles.

Fix:

ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;
CREATE POLICY "owner_select" ON <table>
  FOR SELECT USING (auth.uid() = owner_id);

2. Supabase storage buckets publicly listable

If a table leaks, storage usually leaks too. The same anon key that can SELECT from an unprotected table can also POST to /storage/v1/object/list/<bucket> and enumerate every file in a bucket. On the worst app in our batch we found 8 buckets listable — user avatars, receipts, task attachments, chat files, business cover images, event attachments.

Fix: In the Supabase dashboard under Storage → Policies, scope the SELECT (and UPDATE/DELETE) policies to authenticated users or the specific owner. The default of "no policies + RLS-on" leaves the bucket inaccessible — the failure mode is a misconfigured "allow all" policy or RLS off on the storage tables.

3. Supabase anon key mistaken for a secret

The opposite mistake: people see SUPABASE_ANON_KEY in the JS bundle, panic, and try to "hide" it (sometimes by rotating it repeatedly, sometimes by adding env-var indirection, sometimes by writing a server proxy to forward calls). The anon key is designed to be public — it's a JWT with role: anon in the payload, and RLS does the actual authorization.

The Supabase key that should never ship to the browser is service_role — same JWT shape, but with role: service_role in the payload. That key bypasses RLS on every table. Decoding the middle segment of any eyJ... JWT and checking the role field disambiguates the two.

We didn't observe a service_role leak in this Lovable cohort — but we've seen the pattern in adjacent ones, and our scanner now decodes JWT payloads to flag service_role specifically.

Fix: Keep the anon key client-side, enable RLS, and never paste a service_role key into any client-visible env var (including Lovable secrets that get baked into the bundle).

4. Missing security headers (every app)

Lovable's edge doesn't set HSTS, X-Frame-Options, CSP, or Referrer-Policy on its *.lovable.app subdomains. Every single app we scanned has the same stack of MEDIUM findings for this. Browser-level clickjacking and MIME-confusion protections aren't there by default.

Fix: Mostly on Lovable to set as platform defaults. If you've moved your app to a custom domain behind Cloudflare, set the headers there instead — Cloudflare → Rules → Transform Rules → Modify Response Header.

5. Debug/admin routes visible in the bundle

Less common but high-impact. We've seen JS code branching on if (user.is_admin) where the admin UI was fully shipped to the browser — including the API calls it makes. Disabling the admin UI client-side is not security: an attacker reads the bundle, sees the /api/admin/delete-user call, and invokes it directly. If the endpoint doesn't recheck auth + role server-side, they're in.

Fix: Check auth.uid() + role inside every Supabase RPC function and every RLS policy. Assume the client is hostile.

The meta-lesson

Lovable + Supabase is a great stack. The failure mode isn't the stack — it's that developers add new tables over time and forget RLS is per-table. A template-level lint rule from Lovable ("this table has no RLS policy") would catch every CRIT we've reported.

Until that lands, run a scan before you launch anything that has a table you didn't create on day one.

Run the same scan on your app

One free scan, no credit card. Works with any URL or IP — finds the issues from this post and more.

Start free