Lovable will build the admin panel and leave it unlocked

Lovable is genuinely good at the hard part. Ask for login and you get working login. The gap is smaller and more annoying than people expect: it will happily put your OpenAI key in the browser, hide an admin button instead of protecting it, and make your uploads bucket public — because each of those is the shortest path to a working preview. Here are the four, and how to check yours in about ten minutes.

1. Your API keys are in the page

You asked for AI features. The model needed an OpenAI key, so it put one in the frontend and called the API from the browser. It works perfectly. It also means the key is in the JavaScript you serve to everybody.

People scan for this automatically. A leaked OpenAI key gets found and drained, usually within hours, and you find out from the bill. Same story for Stripe secret keys, Resend, Twilio, and anything labelled service_role.

The rule is short: if a key can spend money or bypass permissions, it never touches the frontend. It goes in a Supabase Edge Function, and the browser calls your function instead of the vendor.

2. The admin check is in the UI, not the app

You asked for admin-only features. What you usually get is a conditional that hides the admin section unless the logged-in user is you.

That is a visual change. The route still exists, the query still runs, and the endpoint still answers anyone who calls it. Anyone can open the network tab, watch what a request looks like, and replay it with their own account — no tooling, no exploit, just a copied request.

A real check lives somewhere the browser cannot reach: a database policy that scopes rows to the user, or a server-side check inside an Edge Function. If your only defence is that the button is not rendered, you do not have one.

3. Your uploads bucket is public

This one gets missed constantly because nothing about it looks wrong. Supabase Storage buckets can be public or private, and a public bucket serves every file in it to anyone with the URL — no login, no token.

Public is the right setting for avatars and product images. It is the wrong setting for the thing your app probably also stores: ID photos, invoices, CVs, medical forms, anything a user uploaded assuming you would keep it. And the URLs are predictable enough to walk.

Split them. One public bucket for decoration, one private bucket for user files, served through signed URLs that expire. In Supabase this is Storage → your bucket → toggle off Public bucket.

4. Your payment webhook believes anyone

Only relevant if you took the Stripe route, but it is the most expensive item here.

Stripe tells your app about payments by POSTing to an endpoint you expose. That endpoint is public — it has to be. Which means anyone who guesses the URL can POST it too, and if your handler does not verify the signature Stripe sends, it will cheerfully mark an order paid because a stranger said so.

The fix is one call to stripe.webhooks.constructEventwith your webhook signing secret before you trust a single field in the body. Ask for it explicitly — this is not something a "set up payments" prompt reliably gives you.

And yes, Row Level Security

You cannot write about Lovable and skip this. CVE-2025-48757, published in 2025, found 170+ Lovable-built apps whose tables had no Row Level Security, which made them readable — often writable — by anyone with the public anon key. Lovable fixed its code generation and added a scanner.

The important detail: that fix applies to new projects. It does not reach into a database you built before it, so an older app is unverified until you verify it. If you only read one more thing, read how Row Level Security actually fails — including why a broken read policy returns an empty list instead of an error, which is why nobody notices.

Then watch it, because you are going to keep prompting

Fixing the four above is a one-afternoon job. Keeping them fixed is not, because you will keep building, and a prompt that adds a feature can quietly move a boundary. These are the changes worth an alert:

Where Defencecore fits

Items 1 to 4 are yours to fix — no monitoring tool can decide that a bucket should have been private. Defencecore covers the part that comes after: it reads your Supabase logs continuously and opens an incident with the evidence attached when a boundary actually moves. It is read-only, so it can explain a problem and never cause one.

Frequently asked questions

Is my Lovable app secure by default?
Parts of it. Lovable handles login properly and now sets up Row Level Security on new projects. What it does not do is decide which of your keys are secret, who is allowed to call your admin routes, or whether an uploaded file should be public. Those are product decisions, and it makes the fast choice unless you say otherwise.
I can see my Supabase key in the browser. Have I been hacked?
No. The anon key is meant to ship in the browser — every Supabase app has one there. The keys that matter are your OpenAI, Stripe, Resend or service_role keys. Those start with sk_ or are labelled service_role, and none of them should ever appear in a page you serve to the public.
The admin button is hidden for normal users. Isn't that enough?
No. Hiding a button removes it from the screen, not from the app. Anyone can open the network tab, see the request the button would have made, and send it themselves — a hidden control and a protected one look identical until someone tries. The check has to run on the server or in a database policy.
What was CVE-2025-48757?
A Row Level Security gap in Lovable-built apps, published in 2025. Because projects shipped a public anon key against tables with no policies, anyone could read and often write the data directly. Research found more than 170 affected apps leaking emails, payment details and API keys. Lovable changed what new projects generate — but it did not rewrite databases that already existed.