Six Ways a Lovable App Leaks Data Before You Even Launch

Lovable builds working apps fast. Working and safe are different tests, and only one of them shows up in the preview window. Here are the six failures I find most often, and how to check for each.

CJ
CJ
·Blog

Lovable is good at its job. You describe an app, it builds one, and the preview window shows something real that you can click through. For a lot of people it's the first time an idea in their head turned into software they can use.

The problem is what the preview window can't show you.

A preview proves your app works when you use it correctly. Security is about what happens when someone uses it incorrectly on purpose. Those are different tests, and the second one never runs while you're building.

I've looked at a lot of these apps now. The same six failures come up over and over, and none of them are exotic. Here they are, roughly in order of how much damage they do.

1. Row Level Security is off, so your database is public

This is the big one, and it's first because it's both the most common and the most severe.

If your Lovable app uses Supabase, your database is reachable over a public REST API. The key your app uses to talk to it, the anon key, ships inside your JavaScript where anyone can read it. There is no server in the middle checking permissions.

The only thing standing between your users table and the internet is a Postgres feature called Row Level Security. When it's off, every row in that table is readable by anyone who opens dev tools.

Check it in the Supabase SQL Editor:

select tablename, rowsecurity as rls_enabled
from pg_tables
where schemaname = 'public'
order by rls_enabled;

Anything returning false is public. I wrote a full guide to fixing this with policies, because it's too much detail for one bullet point here.

2. A policy exists, but it says using (true)

This one is worse than having no policy, because it looks solved.

The dashboard shows RLS enabled. There's a named policy sitting on the table. Everything reads as green. And the policy is this:

create policy "enable read access for all users"
on profiles for select
using (true);

using (true) means every row passes the check. The table is exactly as public as it was before, with a reassuring badge on top.

This shows up in AI-generated migrations constantly, and the reason is worth understanding. At some point your data stopped loading, you said so, and the model produced the change that made it load again. That's a correct solution to the problem you described. You described the wrong problem.

Read every policy on every table and ask whether you meant "everyone." For a public blog post table, sure. For anything with a user_id column, almost never.

3. The service_role key ended up in the browser

Supabase issues a second key called service_role that ignores RLS completely. It's meant for server-side admin work.

It gets into client code in two ways I see regularly. Either it was pasted into an environment variable prefixed with NEXT_PUBLIC_, which publishes it to the browser by definition. Or debugging got frustrating, someone swapped keys to prove the query itself worked, and the swap never got reverted.

Search for it:

grep -rn "service_role\|SERVICE_ROLE" src/ .env* 2>/dev/null

If it appears anywhere that reaches the browser, rotate it in the Supabase dashboard now. Not after launch. A leaked service_role key is unrestricted read and write access to your entire database, and no policy you write will stop it.

4. Validation lives only in the form

Your form requires an email. It caps the bio at 200 characters. It won't let anyone set their own role to admin.

None of that is enforcement. It's all running in the browser, and the browser belongs to the user. Anyone can open dev tools and call your Supabase client directly with whatever values they like, or just POST to the REST endpoint and skip your app entirely.

Real enforcement lives in two places: with check conditions in your RLS policies, and constraints on the table itself.

-- The database refuses a role it doesn't recognise
alter table profiles
add constraint valid_role
check (role in ('user', 'editor', 'admin'));

Now it doesn't matter what request someone crafts. Postgres rejects it.

A useful habit: for every form field, ask what happens if someone sends a different value. If the answer is "the form won't let them," you haven't answered the question.

5. Anyone can promote themselves to admin

This is the specific version of the previous point, and it deserves its own entry because the damage is total.

If your profiles table has a role column, and your update policy is the usual auth.uid() = user_id, then users can edit their own row. Their role is in their own row. So they can set it to admin.

The policy is doing exactly what it says. It just says the wrong thing.

Fix it by making the sensitive column unwritable from the client:

revoke update (role) on profiles from authenticated;

Role changes now have to go through server-side code holding the service_role key, which is where that kind of decision belongs.

While you're in there, check what your app does immediately after signup. If a new user's role is set client-side, that value came from the browser and can't be trusted. Default it in the database instead.

6. Your API keys are in the frontend

Supabase is the one people worry about. It's usually not the one that costs money.

If your app calls OpenAI, Stripe, Resend, or any paid API from client-side code, that key is in the bundle. Anyone can extract it and spend against your account. OpenAI keys in particular get scraped from public repos and burned through fast.

The rule is simple: any key that costs money or grants write access belongs on a server. In a Next.js app that means a route handler or a server action, with the key in an environment variable that has no NEXT_PUBLIC_ prefix.

Confirm it rather than assuming. Build the app, then search the output:

npm run build
grep -rn "sk_live\|sk-proj\|sk-ant\|re_" .next/static/ 2>/dev/null

Anything that turns up is public. Rotate it.

Why AI builders produce this specific set of problems

None of this is a knock on Lovable. It's a consequence of how the feedback loop works.

You are steering with one signal: does the app do the thing. The model optimises for that signal, because it's the only one you're giving it. An insecure app and a secure app both satisfy it perfectly. They look identical in the preview.

Security has no equivalent feedback. Nothing turns red. No error appears. The app that leaks every user's email behaves exactly like the app that doesn't, right up until someone notices, and by then it's a disclosure problem rather than a bug.

So the check has to be deliberate. It won't happen as a side effect of building.

Do this before you launch

Roughly fifteen minutes if you work through it in order.

  1. Run the pg_tables query. Enable RLS on anything showing false.
  2. Read every policy. Flag every using (true) and confirm you meant public.
  3. Grep for service_role in client code. Rotate if found.
  4. Add database constraints for any field where an unexpected value would matter.
  5. Revoke client updates on role or any other permission column.
  6. Build the app and grep the output for third-party API keys.
  7. Create two test accounts. Log in as each. Confirm neither can see the other's data.

Step 7 is the one that actually proves something. Everything above it is a guess until two real accounts sit side by side and fail to see each other.

Frequently asked questions

Is Lovable secure? Lovable generates working code, and the code it generates can be secure or insecure depending on what you asked for. The gap isn't in the tool, it's that the tool has no way to know which tables hold private data unless you tell it. Treat the output the way you'd treat code from a fast contractor who has never met your users.

Does adding a login page protect my data? No. A login page protects your interface. Your Supabase REST API sits at a public URL and accepts the anon key from your JavaScript. Anyone can query it directly without loading your app.

Can I just ask Lovable to make my app secure? It helps, and it's worth doing, but it isn't sufficient. The model can't verify the result, and neither can you from the preview. Ask for the changes, then run the checks above against the actual database.

What's the single most important fix? Row Level Security. Every other item on this list leaks one category of data. RLS being off leaks all of it at once.


If you'd rather not work through this manually, our free security scan points at a live Lovable app and reports exposed tables, permissive policies, and leaked keys, ranked by severity.

Learning to answer these questions yourself, before someone else does, is most of what VibeMastery teaches.

Read Next

Stop guessing. Start shipping.

200+ students use VibeMastery to turn AI prototypes into production apps, with a system instead of luck.

Lifetime Access14 Hours of VideoPrivate Discord
Get VibeMastery — $169

One-time payment · Lifetime access

67%
Off