SCAN YOUR LOVABLE APP FOR VULNERABILITIES
Security checklist for Lovable.dev apps: RLS on every table, no secret keys in the bundle, auth on every route, and BOLA tests across user IDs.
SCAN YOUR LOVABLE APP NOW
Enter your Lovable deploy — we run the security checklist against the live app, not the docs.
Lovable turns a prompt into a deployed React frontend on top of a Supabase backend, live at a public URL in one session. It writes the tables, the auth wiring, the CRUD, and the Edge Functions for you. The problem is that “works” and “hardened” ship at the same moment: the app that renders correctly in the preview is the same app strangers can hit the second you share the link.
Lovable’s security model rests almost entirely on Supabase Row Level Security. The Supabase URL and anon key ship in the JavaScript bundle by design — that is not the vulnerability. The vulnerability is that the anon key becomes a read/write key for your whole database the moment any table is missing an RLS policy, and Lovable creates new tables as features are added without consistently protecting each one.
A black-box scan against the deployed URL is the only test that reflects what an attacker actually sees. It exercises the anon key against every exposed table, replays requests across user accounts, and reads the shipped bundle — none of which shows up in the Lovable editor. Self-scoring a mental checklist while logged in as the owner is how teams ship green demos and red production.
Why Lovable apps need a specialized pass
Generic SAST against a downloaded zip misses configuration that only exists in the Supabase project and in the production build:
- RLS policies live in Postgres, not only in git
- Storage bucket privacy is a dashboard toggle
- Realtime channel filters are runtime wiring
VITE_/ client env values only appear after the build ships- Edge Functions may use service role in ways source review under-reads without the live JWT path
VibeEval’s Lovable-oriented scan is built around those seams. It assumes the stack is React + Supabase + public anon key and probes for the failures that dominate incidents on that stack. For product-level trust questions, pair this page with Is Lovable Safe? and the step-by-step How to Secure Lovable guide.
Common vulnerabilities we find in Lovable apps
Missing Row Level Security on Supabase tables
Supabase exposes a PostgREST endpoint over every table. With RLS off, the anon key from your bundle can SELECT * FROM users straight from the network tab. Lovable’s AI adds tables as you add features but does not consistently attach policies, so an app that started secure regresses the moment you ask it to “add a comments table.” This is the recurring shape we see. Find the tables that slipped through, then enable RLS with a policy that matches your auth model; verify with the Supabase RLS Checker.
select tablename from pg_tables
where schemaname = 'public' and rowsecurity = false;
Also check for tables that have RLS on but no policies — that fails closed for normal roles, which can look “secure” while you debug with service role and then open a using (true) policy to “make the UI work.” Prefer explicit owner policies over emergency true.
The anon key with no policy behind it
Because the anon key ships to the browser, its safety depends entirely on RLS being correct on every table it can reach. A single unprotected table turns that public key into full database access. Watch specifically for policies whose body is using (true) — Lovable sometimes “fixes” a broken query by re-opening the table that way. Reject any true policy unless the table is intentionally public (for example a public blog posts table with no PII).
Inventory:
select schemaname, tablename, policyname, roles, cmd, qual, with_check
from pg_policies
where schemaname = 'public'
order by tablename;
Anything that looks like qual = 'true' on a multi-tenant table is a ship blocker.
BOLA / IDOR in generated CRUD
Generated resource endpoints filter by ID but skip the “does this user own this ID” check. Sign in as user B, take a request that returns your own record — a Supabase call with ?id=eq.<uuid> or a route like /api/projects/abc-123 — swap in user A’s ID, and refire. If A’s data comes back, that is a BOLA. Fix it with an RLS policy that scopes rows to the caller, or an explicit ownership check in the Edge Function.
create policy "owner read" on public.projects
for select using (auth.uid() = owner_id);
Repeat for update and delete. Read-only policies with open writes are a common half-fix. Also test filters like ?owner_id=eq.<victim> — PostgREST will happily return rows if RLS allows it.
Service-role key bypassing RLS
Lovable sometimes generates an Edge Function or RPC that uses the service_role key to “make things easier.” Service role bypasses every RLS policy you wrote, so a lookup-by-ID inside that function returns whatever is asked for. Worse, the key sometimes lands in a chat transcript or a committed file. Create the function’s client with the request’s JWT (Authorization: req.headers.get('Authorization')) so RLS still applies, and rotate any service-role key that ever left Supabase.
// Prefer user-scoped client in Edge Functions
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_ANON_KEY")!,
{
global: {
headers: { Authorization: req.headers.get("Authorization")! },
},
},
);
If you truly need service role (admin jobs, webhooks), keep it only in server secrets, never in VITE_ vars, and implement your own authorization before any query.
Third-party keys in the client bundle
Vite inlines anything prefixed VITE_ into the JavaScript, so Stripe secret keys, OpenAI keys, and Resend keys wired into the frontend end up readable by anyone who views source. Harvesting bots find them within minutes of deploy. Anything other than the Supabase anon key and a publishable Stripe key belongs behind an Edge Function. Confirm with the Token Leak Checker.
# After a production build, fail CI on secret-shaped strings
grep -rE 'sk_(live|test)_|sk-[A-Za-z0-9]{20,}|service_role' dist/ || true
Public storage buckets and unfiltered Realtime
The “let users upload an avatar” feature defaults to a public bucket — fine for avatars, dangerous for anything scoped to a user. Bucket privacy and policy enforcement are two separate switches, so a “private” bucket with no policy still lists and downloads. Likewise, a Realtime channel subscribed to a table with permissive RLS broadcasts every row change to every connected client. Scope buckets with per-owner storage policies and pin Realtime channels with a row filter.
-- Example: users only read their own objects under folder user_id/
create policy "own objects" on storage.objects
for select using (
bucket_id = 'docs'
and auth.uid()::text = (storage.foldername(name))[1]
);
For Realtime, subscribe with filters that match RLS assumptions — and remember that if RLS is wrong, Realtime will happily amplify the leak to every connected browser.
Auth UI that is not authorization
Lovable apps often look “logged in only” because React redirects unauthenticated users to /login. Direct PostgREST calls with the anon key ignore that UI entirely. So do deep links to Edge Functions. Always test with the network tab and with cookies cleared — not only by clicking around as the happy path user.
Password and session soft spots
Where Lovable wires Supabase Auth: confirm email verification is on for production, leaked-password protection is considered (Lovable password protection), and session handling does not stash long-lived tokens in places XSS can trivially read if you later add unsafe HTML rendering.
How VibeEval works with Lovable
- Enter your deployed Lovable URL (
*.lovable.appor a custom domain). Optionally provide two test logins so the agent can run cross-user checks. - The agent drives a real browser through the app. It maps routes and Supabase calls, exercises the anon key against every reachable table, replays authenticated requests with a second user’s IDs to probe BOLA, reads the shipped bundle for leaked keys, checks security headers, and guesses storage-bucket paths.
- You get a report of findings ranked by severity, each with the concrete evidence (the table that answered anonymously, the ID that leaked another user’s row) and a paste-ready fix prompt — an RLS policy, an Edge Function change — you can feed straight back into Lovable.
After you paste the fix prompt and Lovable regenerates, rescan the same URL. Regeneration is exactly when policies regress: a follow-up prompt that “fixes the invoices list” can re-open a table that a previous scan closed.
Manual testing vs VibeEval
| Dimension | Manual review | VibeEval scan |
|---|---|---|
| Time per full pass | Hours across dashboard, bundle, and every route | Minutes against the deployed URL |
| Cross-user BOLA coverage | Tedious; needs two accounts and per-endpoint replay | Automated ID swap across every ID-keyed request |
| RLS coverage after a regeneration | Easy to forget the one table Lovable just changed | Every reachable table re-tested each run |
| Bundle key leaks | Manual grep, easy to miss rotated-in keys | Every deploy re-scanned |
| Business-logic flaws | Human judgment still required | Not a substitute — pairs with manual review |
| Repeatability | Depends on discipline after each prompt | Identical pass on demand |
Manual review still owns business logic and intent. The scanner wins on repeatability: every Lovable prompt can rewrite security-critical code, so the check that matters is the one you can run again in full after each edit.
A lightweight manual pass when you cannot scan yet
- Supabase → Authentication → confirm email confirm is required in prod.
- Table editor → every table → RLS enabled with non-
truepolicies. - Storage → buckets → private unless intentionally public; policies present.
- Project settings → API → confirm
service_rolenever appears in the Lovable frontend code or env UI marked for client. - Browser DevTools → Sources → search
sk_,service_role,OPENAI. - Two browser profiles → swap resource IDs in network requests.
Lovable-specific regression triggers
Re-run a full scan after prompts that:
- Add or rename tables / columns
- Touch “admin,” “roles,” or “team” features
- Add file upload, chat, or Realtime
- Connect Stripe, OpenAI, Resend, or other third parties
- “Make it work for all users” or “fix the empty list”
- Change Edge Functions or “backend logic”
Those phrases correlate strongly with opened policies and client-side secrets.
Frequently asked questions
Does VibeEval work with custom domains?
Yes. VibeEval scans any deployed Lovable app whether it uses the default lovable.app subdomain or a custom domain.
Can VibeEval check my Supabase RLS policies?
The scanner does black-box RLS testing — it exercises the anon key against your tables the way an attacker would and reports which answer without authorization. For direct policy auditing against your schema, connect your Supabase project and use the Supabase RLS Checker.
Why scan if the app works in the Lovable preview?
The preview runs as you, authenticated, with data you own. It never tests what the anon key can reach, what a second user can read, or what ships in the public bundle. Those are exactly the gaps that reach production.
How often should I scan?
After every deploy, and specifically after any prompt that touched the database or an Edge Function. Lovable rewrites server code on each prompt, so a policy that passed yesterday can regress today.
Will scanning affect my production app?
No. VibeEval uses non-destructive probing — it reads and replays requests but never modifies or deletes data.
What if I only have one test account?
You can still catch anonymous RLS failures and bundle key leaks. Cross-user BOLA needs two identities; create a second free account before launch — it is five minutes that prevent the most common multi-tenant bug.
Does a clean scan mean the app is “secure”?
It means the automated probes did not find the common Lovable failure classes on that URL at that time. Business logic abuse, sophisticated auth bypass chains, and brand-new tables added after the scan are still on you. Use the scan as a release gate, not a certificate.
Mapping findings to fixes you can paste into Lovable
| Finding class | Typical fix direction |
|---|---|
| Table without RLS | Enable RLS + owner policies for select/insert/update/delete |
using (true) policy |
Replace with auth.uid() = owner_id (or team membership) |
BOLA on /resource/:id |
Ownership in RLS or Edge Function before return |
service_role in client |
Rotate key; move logic to Edge Function with user JWT |
| OpenAI/Stripe secret in bundle | Edge proxy; client only holds publishable/anon keys |
| Public private docs bucket | Private bucket + signed URLs or path-scoped policies |
| Realtime leak | Row filters + fix underlying RLS |
Paste-ready prompts from the scanner encode these directions with your table names already filled in — faster than describing the stack again to the model from scratch.
Pre-launch Lovable gate (critical-first)
- No tables with RLS off (or with empty /
truepolicies on private data). - No
service_roleor third-party secrets in the client bundle. - Two-user BOLA clean on every ID-keyed path.
- Storage: no accidental public sensitive buckets.
- Auth: verification and password policies appropriate for production.
- Live VibeEval scan green on critical/high for the production domain.
If any of those fail, do not share the link for real user signup. A working demo is not a launch.
Related Lovable resources
- How to Secure Lovable — full hardening guide for the Supabase stack.
- Is Lovable Safe? — the three gaps in every Lovable app and how to fix each.
- Lovable Security Checklist — pre-launch checklist, critical-first.
- Is My Lovable App Secure? — a fast self-assessment.
- Lovable Tech Stack — what Lovable ships and where the seams are.
- Supabase RLS Checker — verify every table has a correct policy.
- BOLA in AI-Generated CRUD — the ownership-check failure in depth.
- Token Leak Checker — client bundle secret scan.
- Vibe Code Scanner — multi-platform surface when you leave pure Lovable hosting.
Shipping cadence that keeps Lovable apps honest
Lovable’s prompt loop rewrites server and schema code without a human threat model. Define a minimum gate for every deploy that touches data:
- SQL: list public tables with
rowsecurity = false— must be empty (except intentional public catalogs). - Bundle: search built assets for
service_role,sk_live,sk-, PEM blocks. - Dual-user: user A cannot read user B’s primary resources by ID.
- Storage: private buckets deny anonymous GET.
- Edge Functions: no
--no-verify-jwtexcept documented webhooks. - Live URL scan on production and any long-lived preview that shares prod data.
Write these six as a PR template checkbox. Agents will not run them unless you require the checkbox.
Policy templates for common Lovable tables
-- profiles
alter table public.profiles enable row level security;
create policy profiles_select on public.profiles for select using (auth.uid() = id);
create policy profiles_update on public.profiles for update
using (auth.uid() = id) with check (auth.uid() = id);
-- org-scoped documents
alter table public.documents enable row level security;
create policy documents_member_select on public.documents for select using (
org_id in (select org_id from public.memberships where user_id = auth.uid())
);
After Lovable adds columns like is_admin, forbid client updates with column-level privileges or triggers — never trust a client-set role flag.
When the product is multi-tenant
Single-user user_id = auth.uid() policies are insufficient for B2B. Add membership tables early; backfill is harder after launch. Test a user removed from an org still cannot read org rows (and that realtime unsubscribes).
Evidence pack for customers
Security questionnaires ask “do you have access control testing?” Keep dated scan reports, the dual-user script output, and the migration files that enable RLS. That package is more convincing than “we use Supabase.”
Test your Lovable app before launch
The gap between a working Lovable app and a hardened one ships to production in the same click. Scan the deployed URL before you share it — RLS coverage, the anon key’s reach, cross-user BOLA, and leaked bundle keys, in one pass.
TURN THE CHECKLIST INTO EVIDENCE
Don't self-score. We probe RLS, keys, and auth on your live Lovable URL and return proof of what is still open.
14-day free trial · No credit card · Cancel anytime