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

  1. Enter your deployed Lovable URL (*.lovable.app or a custom domain). Optionally provide two test logins so the agent can run cross-user checks.
  2. 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.
  3. 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

  1. Supabase → Authentication → confirm email confirm is required in prod.
  2. Table editor → every table → RLS enabled with non-true policies.
  3. Storage → buckets → private unless intentionally public; policies present.
  4. Project settings → API → confirm service_role never appears in the Lovable frontend code or env UI marked for client.
  5. Browser DevTools → Sources → search sk_, service_role, OPENAI.
  6. 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)

  1. No tables with RLS off (or with empty / true policies on private data).
  2. No service_role or third-party secrets in the client bundle.
  3. Two-user BOLA clean on every ID-keyed path.
  4. Storage: no accidental public sensitive buckets.
  5. Auth: verification and password policies appropriate for production.
  6. 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.

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:

  1. SQL: list public tables with rowsecurity = false — must be empty (except intentional public catalogs).
  2. Bundle: search built assets for service_role, sk_live, sk-, PEM blocks.
  3. Dual-user: user A cannot read user B’s primary resources by ID.
  4. Storage: private buckets deny anonymous GET.
  5. Edge Functions: no --no-verify-jwt except documented webhooks.
  6. 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

RUN LOVABLE SCAN