Lovable makes it easy to go from prompt to a working app with a Supabase backend in an afternoon. The catch is that "it works" and "it is safe to put real users on it" are different bars. Most security problems in Lovable apps are not exotic hacks. They are a handful of configuration gaps that anyone with browser devtools can find. This guide walks through what to check, in order of how much damage each one can do.
1. Turn on Row Level Security for every Supabase table
This is the big one. A Lovable frontend talks to Supabase directly from the browser using the project URL and the anon key. Both are visible to every visitor: open devtools, look at network requests, and they are right there. That is by design. The thing that stops a visitor from running select * from profiles with that key is Row Level Security (RLS).
If a table has RLS disabled, anyone can read and write every row through the REST API. If RLS is enabled but a policy says using (true), the effect is the same for that operation.
How to check:
- In the Supabase dashboard, open Database → Tables. Any table marked "RLS disabled" in the public schema is exposed.
- Open the Security Advisor (Advisors → Security). It flags tables without RLS and policies that are overly permissive.
- Read each policy. User-owned data should be scoped, for example
using (auth.uid() = user_id)for select, and a matchingwith checkfor insert and update.
alter table public.notes enable row level security;
create policy "Users read own notes" on public.notes
for select using (auth.uid() = user_id);
create policy "Users write own notes" on public.notes
for insert with check (auth.uid() = user_id);
Test it for real: sign in as user A, copy the request from the network tab, and change the ID to a row owned by user B. You should get an empty result, not B's data.
2. Make sure no real secrets are in the frontend
The anon key is fine to expose. These are not: the Supabase service_role key, OpenAI or Anthropic API keys, Stripe secret keys (sk_live_…), Resend or SendGrid keys, and database connection strings. If any of these end up in client code, anyone can copy them from your JavaScript bundle and use them on your bill or against your data.
When you ask an AI builder to "call OpenAI", it sometimes puts the key straight into a React component. The safe pattern is a Supabase Edge Function (or another server route) that holds the key as a secret and that your frontend calls. Search your synced GitHub repo for sk-, sk_live, service_role and eyJ (JWTs) to find stragglers. If a secret was ever committed or shipped, rotate it. Deleting it from the code is not enough. See our guide to exposed .env files and API keys.
3. Check authentication settings
- Email confirmation: decide deliberately whether it is on. If it is off, anyone can sign up with someone else's address.
- Redirect URLs: in Supabase Auth settings, restrict Site URL and Additional Redirect URLs to your real domains, not wildcards.
- Admin checks: "admin" features hidden in the UI are not protected. If the database does not enforce the role through RLS or a server function, a user can call the API directly.
- Rate limits: review Supabase Auth rate limits and enable CAPTCHA on sign-up if you expect bots.
4. Lock down storage buckets
Supabase Storage buckets can be public or private, and private buckets use RLS policies on storage.objects. User uploads such as invoices, IDs or avatars of private accounts should live in a private bucket served through signed URLs. A public bucket means anyone with the file path can download the file.
5. Add security headers
Headers are a cheap second layer. Content-Security-Policy limits where scripts can load from, which blunts XSS. Strict-Transport-Security forces HTTPS. X-Frame-Options or frame-ancestors stops clickjacking. If you host on Lovable's own domain, your control over headers is limited. If you export to GitHub and deploy on Vercel or Netlify, you can set them yourself. Follow our Vercel headers guide or Netlify headers guide.
6. Don't leak data in error messages and logs
Check that Edge Functions return generic errors to the client and log details server-side only. Remove console.log calls that print tokens, user objects or API responses in production builds.
7. Run an outside-in scan
Once the fixes are in, look at your app the way an attacker would: from the outside, with no special access. ShipCheck does this for free. It fetches your live site and the JavaScript it ships, looks for exposed secrets and committed .env files, checks HTTPS and security headers, and reports mobile speed, all with copy-paste fixes. It is passive and read-only. It does not test your RLS policies for you, so do step 1 by hand.
Quick checklist
- RLS enabled on every public table, with no
using (true)on private data - Supabase Security Advisor shows no errors
- No service_role, OpenAI, Stripe secret or other private keys in frontend code or git history
- Auth redirect URLs restricted; email confirmation decided on purpose
- Private storage buckets for private files
- Security headers set where you control hosting
- External scan comes back clean