Is my Lovable app secure?

Lovable gets you a working app fast. Working and safe are different targets, and the gap between them is usually four or five specific things. Here's what to check, in the order that matters.

None of this is a criticism of Lovable or of you. An AI builder optimises for what you asked for โ€” an app that works. Nobody prompts "and don't put the service_role key in the client bundle", because if you knew to ask that, you'd have known not to do it.

1. Row Level Security, before anything else

Lovable apps almost always use Supabase, and Supabase is safe by configuration, not by default. Your app talks to the database from the browser using the anon key. That key is meant to be public โ€” it's in everyone's page source and that's fine โ€” which means the only thing standing between a stranger and your data is Row Level Security.

If RLS is off on a table, anyone who opens DevTools can read, edit or delete every row in it. Not just their own rows. Every row.

Check this first. In Supabase, open Table Editor and look at every table. Any table without RLS enabled is fully public right now. Turning RLS on with no policies makes a table fully private โ€” you then add policies to let the right people back in.

Enabling RLS is not the finish line. A policy of USING (true) is the same as no protection at all. What you want on a user-owned table is a policy scoped to the logged-in user:

alter table todos enable row level security;

create policy "own rows only" on todos
  for all
  using (auth.uid() = user_id)
  with check (auth.uid() = user_id);

The with check half matters as much as using. Without it, someone can pass the read test and then write a row belonging to somebody else.

Prompt to paste into Lovable:

Review every table in my Supabase project. List which have Row Level Security disabled, and which have policies that would let one user read or modify another user's rows. For each problem, give me the SQL to fix it.

2. The service_role key must never reach the browser

Supabase gives you two keys. The anon key is public by design. The service_role key bypasses Row Level Security entirely โ€” every policy you just wrote stops applying.

It belongs in server-side code only: an edge function, or a backend route. If it appears anywhere the browser can reach, every visitor has full read and write access to your entire database, and no amount of RLS helps.

If the service_role key has ever been in frontend code, in a VITE_-prefixed variable, or committed to a public repo, treat it as compromised. Rotate it in the Supabase dashboard under Settings โ†’ API. Deleting it from the code does not un-leak it.

3. Anything with a public prefix is public

Variables starting VITE_, NEXT_PUBLIC_ or REACT_APP_ get compiled into the JavaScript bundle you ship. They are not configuration hidden on a server โ€” they're constants in a file anyone can read.

So VITE_SUPABASE_ANON_KEY is fine. VITE_OPENAI_API_KEY or VITE_STRIPE_SECRET_KEY means you've published a credential, even though it looked like an environment variable and environment variables feel private.

The rule that covers all of it: browser code cannot keep a secret. If the frontend can read it, so can everyone.

4. AI calls need to go through your own backend

If your app calls OpenAI or Anthropic directly from the frontend, the key authorising those calls is shipped to every visitor. The usual tell is dangerouslyAllowBrowser: true โ€” a flag people add because it makes an error message disappear and the app start working. It's named "dangerously" for exactly this reason.

The fix is a Supabase edge function that holds the key and forwards the request. While you're there, add a rate limit: an unprotected AI proxy is someone else's free API credits, and they will find it.

Move my OpenAI calls out of the frontend into a Supabase edge function that reads the key from an environment variable. Update the frontend to call that function instead, remove dangerouslyAllowBrowser, and add basic per-user rate limiting to the function.

5. Before you publish

Check it in about ten seconds

vibecheck scans your project or your live URL for all of the above and tells you what's wrong in plain English โ€” with a fix prompt you paste straight back into Lovable.

Scan my app โ€” free