Lovable security checklist: 8 things to verify before your launch tweet
Lovable ships apps in minutes. The default wiring it generates connects a React frontend directly to Supabase using the anon key. That part is fine — the anon key is supposed to be public. What Lovable doesn't generate is the protection that makes the anon key safe to expose: Row-Level Security policies on every table.
Run through this checklist before your first user signup. The whole thing takes about 15 minutes if nothing's wrong, and most things are quick to fix if they are.
1. service_role key is not in your client bundle
This is the catastrophic one. The service_role key bypasses all Row-Level Security; if it's in your client JS, every visitor can read and write everything.
Test:
curl -sL https://your-app.lovable.app/ | grep -oE 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'
For each JWT, decode the middle segment with echo <segment> | base64 -d. If any decoded payload contains "role":"service_role", you have a P0.
Fix: see our service_role key leak response guide. Rotate immediately, then audit your codebase for any place the old key was pasted. The most common cause in Lovable apps is using a NEXT_PUBLIC_ or VITE_ prefix on the wrong env var.
2. Row-Level Security is enabled on every table
RLS is opt-in per table. Lovable doesn't enable it. The default is "anyone with the anon key can read and write."
Test: Supabase Dashboard → Database → Tables. Each table row should show "RLS enabled" — not "RLS disabled." Or in SQL:
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public';
Every row should have rowsecurity = true.
Fix:
ALTER TABLE public.<table_name> ENABLE ROW LEVEL SECURITY;
Important: enabling RLS without writing any policies denies all access to anon and authenticated. That's safer than the previous state, but your app will break until you write policies. Run a vibecheck scan with autofix to get suggested policies for your schema.
3. RLS policies actually prevent unauthorized reads
RLS enabled with a "true" policy is the same as no RLS. The classic mistake:
CREATE POLICY "everyone_can_read" ON public.users
FOR SELECT USING (true); -- this protects nothing
Test from outside: open your terminal and try to read a sensitive table using just the anon key from your bundle:
curl 'https://<your-ref>.supabase.co/rest/v1/users?select=*&limit=5' \
-H 'apikey: <anon-key>' \
-H 'Authorization: Bearer <anon-key>'
If you get user data back, your policy is wrong. The fix depends on what the table is for; the vibe coding security guide covers the five common patterns. For a user-owned table:
CREATE POLICY "users_select_own" ON public.users
FOR SELECT TO authenticated
USING (auth.uid() = id);
4. Storage buckets aren't fully public unless you mean it
Lovable apps that handle user uploads — avatars, documents — frequently end up with a Supabase Storage bucket marked "Public." This makes every file readable by URL guess.
Test: Dashboard → Storage. Each bucket has a "Public" toggle. For each public bucket, ask yourself: do I want every file in here URL-guessable? For an avatar bucket, yes. For a "user documents" bucket, no.
Fix for non-public buckets: toggle Public off. Then write Storage RLS policies that grant read access only to the file owner:
CREATE POLICY "users_read_own_files" ON storage.objects
FOR SELECT TO authenticated
USING (auth.uid()::text = (storage.foldername(name))[1]);
This assumes you're storing files in a {user_id}/ folder structure, which is the Supabase recommended pattern.
5. Edge Functions check authentication
Lovable doesn't always generate Edge Functions, but if yours does, every function that touches user data needs to verify the JWT. The default Edge Function template does not do this — it's open by default.
Test: for each function, send an unauthenticated request:
curl -X POST 'https://<ref>.supabase.co/functions/v1/<function-name>' \
-H 'Content-Type: application/json' \
-d '{}'
If it returns 200 with data, anyone can call it.
Fix: at the top of each function:
const supabase = createClient(
Deno.env.get('SUPABASE_URL')!,
Deno.env.get('SUPABASE_ANON_KEY')!,
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
);
const { data: { user } } = await supabase.auth.getUser();
if (!user) return new Response('Unauthorized', { status: 401 });
6. CORS is restricted to your domain
Supabase REST and Storage default to Access-Control-Allow-Origin: *. That's fine for the GoTrue auth endpoints (they require the anon key) and for any table covered by RLS (the policies are the protection). But if you have any Edge Function that returns user-specific data based on cookie/session state, restrict CORS in the function.
For pure-Supabase apps without Edge Functions, this isn't usually a P0 — RLS does the protection. But it's worth knowing.
7. Custom domain has HTTPS and a real cert
If you've pointed a custom domain at Lovable, verify the SSL cert is valid (browser shows the lock icon, no warnings) and the DNS isn't proxied through Cloudflare in "DNS only" gray-cloud mode if Lovable does its own SSL.
This is more of a "your launch will look broken on Twitter" issue than a security issue, but it shares the checklist.
8. .env files aren't committed to your Lovable git repo
Lovable projects can be pushed to GitHub. If they are, check that .env, .env.local, .env.production are all in .gitignore. Then check the git history — if any of those files were committed at any point, even a deleted commit, the secrets in them are public on GitHub forever.
Test:
git log --all --full-history -- .env .env.local .env.production
# If any commits show up, secrets in those commits are exposed.
git rev-list --all | xargs git grep -l 'SUPABASE_SERVICE_ROLE_KEY'
# Find any commit that mentions the service_role env var.
Fix: if anything is exposed, you have to rotate every secret that was ever in those files, even ones from months ago. Removing the file from history (git filter-repo) doesn't help — automated scrapers have already pulled it. Treat any secret that's ever been in a public commit as compromised forever.
One command to run them all
The first six items are what vibecheck checks automatically when you submit your URL. Items 7 and 8 require manual verification — but if 1–6 pass and you've done 7 and 8, your Lovable app is in better shape than 80% of what's been launched in the last six months.
Inspect your Lovable app