LOVABLE SECURITY CHECKLIST

Lovable scaffolds a React frontend on top of Supabase and writes most of the backend for you. That speed is the problem: by the time you have a working signup flow, you also have a database with RLS off on three tables, a service_role key sitting in a chat transcript, and an Edge Function that trusts whatever the browser sends it. The checklist below is what we look for first when we audit a Lovable app, in the order it tends to bite.

Treat the Critical section as launch-blocking. The High section is what stops you from getting popped in week one. The Medium section is the cleanup you do once you have users.

How to use this checklist

Walk it top to bottom in the Supabase dashboard plus your Lovable project, ticking items as you go. After each checkpoint, deploy and re-test — Lovable rewrites server code on every prompt, and a passing item on Tuesday can fail on Wednesday because you asked it to “add a delete button”. After the whole list passes, run a black-box scan against the deployed URL to confirm none of the fixes regressed.

Critical (fix before launch)

1. Enable Row Level Security on every public table

Why it matters. Supabase exposes your Postgres database directly to the browser via PostgREST. Without RLS, the anon and authenticated roles can SELECT * FROM users from the network tab. This is the single most common breach in Lovable apps and the easiest to test for.

How to check. In the Supabase dashboard go to Authentication → Policies and confirm every table under public shows the green RLS shield enabled. Run this query in the SQL editor to find tables that slipped through:

select schemaname, tablename
from pg_tables
where schemaname = 'public'
  and rowsecurity = false;

How to fix. Enable RLS on the table, then write at least one policy. RLS without a policy denies everything — that is correct, not broken. Add using (auth.uid() = user_id) for owner-scoped data, or using (true) for read-public tables (and only those).

2. Rotate the service_role key if it ever touched the client

Why it matters. service_role bypasses every RLS policy you just wrote. Lovable will sometimes paste it into chat, into an Edge Function that gets logged, or into a .env file that gets committed. If it ever appeared anywhere outside Supabase Vault, treat it as compromised.

How to check. Search your repo, your Lovable chat history, and your browser bundle for the prefix of the key. In the deployed app, open DevTools → Network and inspect any request — if you see Authorization: Bearer eyJ... and the JWT decodes to "role": "service_role", the key is in the client.

How to fix. Rotate the key in Settings → API, update server-only env vars, and redeploy. Never reference SUPABASE_SERVICE_ROLE_KEY from any file under src/.

3. Move third-party API keys out of the React bundle

Why it matters. Vite inlines anything prefixed VITE_ into the JavaScript bundle. Stripe secret keys, OpenAI keys, Resend keys, and webhook signing secrets end up readable by anyone who views source. Lovable does this by default whenever it wires up an integration in the frontend.

How to check. grep -r "VITE_" src/ and confirm every match is a public-by-design value (publishable Stripe key, public Supabase URL, anon key). Then curl https://your-app.lovable.app/assets/index-*.js | grep -E "(sk_|rk_|sk-proj-|re_)" to spot keys that leaked anyway.

How to fix. Move the integration into a Supabase Edge Function. The browser calls the function with the user’s JWT; the function holds the secret and talks to Stripe/OpenAI/Resend on the server.

4. Lock every storage bucket behind a policy

Why it matters. Public buckets serve anyone who guesses the file path. Even non-public buckets allow listing and download if you forgot the policy — bucket privacy and policy enforcement are two separate switches in Supabase.

How to check. Storage → Policies should show explicit select, insert, update, and delete policies on each bucket. For private buckets, confirm the bucket toggle is off and there is a policy. For public buckets that hold user uploads (avatars, attachments), confirm filenames are unguessable (UUID, not user-id-1.png).

How to fix. Add per-operation policies that scope by auth.uid(). For avatars: ((storage.foldername(name))[1] = auth.uid()::text). Stop relying on filename obscurity.

5. Validate input on every Edge Function

Why it matters. Lovable-generated Edge Functions assume the JSON body is well-formed and the user is who they say they are. They commonly write to the database with service_role and trust the user_id field from the request body — which is a textbook IDOR.

How to check. Open every function under supabase/functions/. For each, confirm: (a) the user identity comes from req.headers.get('Authorization') decoded server-side, never from the body; (b) inputs are validated before being used in queries; (c) the Supabase client is created with the request’s JWT, not service_role, unless the function genuinely needs admin scope.

How to fix. Use createClient(SUPABASE_URL, ANON_KEY, { global: { headers: { Authorization: req.headers.get('Authorization')! } } }) so RLS still applies. Validate body shape with Zod or a manual schema check before the first query.

6. Verify auth flows actually require auth

Why it matters. Lovable will happily generate a “Delete account” page that calls a public RPC, or an “Admin” route that checks localStorage.getItem('isAdmin'). Authorization on the frontend is decoration, not security.

How to check. For every privileged route, drop the auth cookie/token in DevTools and try to use the feature. For every privileged RPC or Edge Function, call it with an anonymous JWT and confirm it returns 401 or 403, not 200.

How to fix. Authorization checks live in RLS policies and Edge Functions. Frontend route guards exist only to redirect users to the login page — they do not protect anything.

High (fix in the first week)

7. Require email verification before granting any role

Disable the “Confirm email” bypass in Authentication → Providers → Email and gate role assignment on auth.users.email_confirmed_at. Otherwise anyone can sign up as attacker@yourcompany.com and inherit whatever permissions you grant new users.

8. Pin OAuth redirect URLs to your production domain

In Authentication → URL Configuration, the “Site URL” and “Redirect URLs” must list only your real domains. Wildcards and localhost entries left over from development let attackers complete OAuth flows on a domain they control and walk away with the user’s session.

9. Add CAPTCHA and rate limits to auth endpoints

Enable hCaptcha or Turnstile in Authentication → Settings. Set per-IP rate limits on signup, password reset, OTP, and magic link endpoints. Without this, you fund someone else’s email enumeration and SMS bill.

10. Set short JWT expiry on sensitive apps

Default Supabase JWTs last an hour, refresh tokens much longer. For anything touching money, health, or PII, drop the JWT TTL to 15-30 minutes and require re-authentication for high-risk actions (password change, payout config, data export).

11. Audit RLS policies after every Lovable regeneration

This is the one Lovable-specific habit that matters most. When you ask Lovable to “add a comments table” it will write a migration and the RLS policies for that table — and may rewrite policies on adjacent tables. After every database-touching prompt, re-run the RLS audit query from item 1 and diff the policies against the previous deploy.

12. Verify webhook handlers check signatures

Stripe, Resend, GitHub, and Supabase webhooks all sign their payloads. Lovable’s first-pass webhook handler usually skips the signature check. Without it, an attacker who knows the URL can post arbitrary events — fake “payment succeeded”, fake “user verified”, whatever your handler trusts.

13. Disable email enumeration

In Authentication → Settings turn on “Confirm email” and “Secure email change”, and configure error messages to be identical for “user exists” and “user does not exist” on signup, password reset, and magic link. Without this, attackers can enumerate which emails have accounts.

Medium (fix when you can)

14. Set Content-Security-Policy headers

Lovable’s hosting layer ships with permissive defaults. Add a CSP that restricts script-src to your domain and Supabase, blocks inline scripts where possible, and forbids unsafe-eval. This is your last line of defense against XSS that slips past validation.

15. Disable Supabase Studio in production

If you self-host or expose Studio under a custom domain, lock it behind SSO or take it offline entirely. Production Studio access is equivalent to root on the database.

16. Enforce password length and breach-list checks

Set the minimum password length to 12 and enable HaveIBeenPwned check in Authentication → Settings. The default 6-character minimum is a regression to 2010.

17. Restrict the anon role to only the tables it needs

Run revoke select, insert, update, delete on table <name> from anon for tables the unauthenticated user should never touch. Belt and braces alongside RLS.

18. Pin dependency versions and audit on every regeneration

Lovable upgrades dependencies as it goes. After each session, run npm audit --production and check for new high/critical findings. Pin versions in package.json once you ship.

19. Set up audit logging on auth events

In Logs → Auth Logs confirm logging is on, then ship those logs to whatever you use for monitoring. You want a record of password resets, role changes, and failed logins long after Supabase’s default retention.

20. Restrict direct database connections to known IPs

In Database → Connection Pooling enable IP allowlisting if your backend services connect directly to Postgres. Disable the pooler for environments that don’t need it.

After every Lovable regeneration

Lovable is not a one-shot tool. Each prompt may rewrite security-critical code. Before you push:

  • Re-run the RLS audit query from item 1.
  • Diff every file under supabase/functions/ against the previous deploy.
  • Search the diff for service_role, VITE_, and any new third-party SDK imports.
  • Re-test the auth-required routes with the auth header stripped.
  • Re-test webhook handlers with an unsigned payload.

Common attack patterns we see in Lovable apps

The “user_id from body” IDOR. Generated Edge Function reads user_id from the request JSON, queries service_role with it, returns whoever’s data was asked for. The fix is to take identity from the JWT, never the body.

The chat-leaked service key. Developer pastes a Supabase URL into the Lovable chat, asks “why isn’t this working”, Lovable suggests using service_role to “bypass the RLS issue”, developer accepts. Key now in chat history, in the bundle, and in three branches.

The forgotten public bucket. Storage bucket created during prototyping with public toggled on, now serving 50,000 user-uploaded ID photos under predictable paths.

The regenerated policy. RLS policy was correct on Monday. Tuesday’s “make this faster” prompt rewrote the policy to using (true) “for performance”. Nobody noticed because nothing visible broke.

How to Secure Lovable

Step-by-step security guide for Lovable apps — covers the Supabase configuration, edge function patterns, and frontend hardening above in long form.

Is Lovable Safe?

In-depth security analysis of the platform itself — what Lovable does well, where the defaults bite, and what we find when we scan the typical Lovable app on the day it launches.

Automate Your Checklist

A checklist tells you what to look for. A scanner tells you what’s actually broken in the deployed app right now. VibeEval drives a real browser through your Lovable app, attempts the IDORs and storage bucket guesses described above, and reports what got through — with the exact RLS policy or function file to fix.

SCAN YOUR LOVABLE APP

14-day trial. No card. Results in under 60 seconds.

START FREE SCAN