Is my Lovable app secure, and how can I check it myself?

The answer

Open your live app, press F12, and watch the Network tab while a page loads your data. If a response contains rows belonging to other users, your database has no working row-level security and anyone can read that data with a browser.

By Muhammad Bilal7 min read

The short version

  • The most common flaw in AI-built apps is not a hacker problem, it is a missing database rule: row-level security was never switched on, so every row is readable by anyone with your public key.
  • You can test for it yourself in a browser, with no tools, no signup and no coding — the four checks below take about five minutes.
  • Your Supabase anon key being visible in the browser is normal and not a leak. Your service_role key being visible is an emergency.
  • A security scanner that says 'fixed' is not evidence of anything. Only a request that comes back empty is evidence.
  • If you find something, rotate keys before you fix code — a key that has been public stays compromised after the bug is patched.

If you built something with Lovable, Bolt, Replit or Base44 and a quiet voice at the back of your head has been asking whether it is safe to put real people's data in it — that voice is right to ask, and the answer is knowable today, by you, without hiring anyone.

This is not a lecture about security in general. It is four specific checks, on your own app, in your own browser. If all four come back clean you can stop worrying about the big one. If one of them doesn't, you will know exactly what is wrong and what it is called, which is most of the battle.

One ground rule before anything else: only run these on an app you own. They are read-only and they change nothing, but they are still a security test, and pointing a security test at someone else's site is a different thing entirely.

Why this is worth five minutes

In March 2025 a researcher named Matt Palmer found that apps generated by Lovable were shipping with database rules that did not restrict anything. It was assigned CVE-2025-48757 and published on 29 May 2025 with a CVSS score of 9.3 — critical. The official description is blunt: an insufficient row-level security policy "allows remote unauthenticated attackers to read or write to arbitrary database tables of generated sites."

Read that again slowly. Unauthenticated means no login. Arbitrary tables means all of them. Write means not only reading your data but changing it.

When Palmer scanned for the pattern afterwards, he found 303 endpoints across 170 projects still exposed — leaking email addresses, phone numbers, payment details and API keys. Lovable disputes the CVE, on the grounds that customers are responsible for securing their own application data.

They are not entirely wrong, and that is exactly the problem. The platform's position is that this is your job. Nobody told you it was your job, or what the job consists of. So here it is.

Check 1: can a stranger read your database?

This is the big one. Two minutes.

Open your live app in Chrome — the real published URL, not the Lovable preview. Log in as a normal user if it has a login. Press F12 and click the Network tab. Tick the Fetch/XHR filter. Now reload the page and click around to something that shows your data: a dashboard, a list, a profile.

You will see requests going to an address like xxxxx.supabase.co/rest/v1/something. Click one and open the Response tab. You are looking at exactly what your database handed to the browser.

Now the question that matters: is anything in that response none of this user's business? Rows belonging to other people. Other customers' email addresses. A users table when the page only needed one user. If the answer is yes, your row-level security is not doing its job, and everything in that response is readable by anyone who knows the address.

There is a second version of this check that is more conclusive. Log out completely, or open the same page in an incognito window with no session at all, and repeat. Anything that still comes back with real data is data available to the entire internet.

Clean result: responses contain only the logged-in user's own rows, and the logged-out attempt returns an empty list [] or a 401.

Check 2: which key is your app carrying?

Still in the Network tab, click any Supabase request and look at Headers. You will find a header called apikey with a very long value, and it will be plainly visible.

Do not panic. That key is meant to be public. It is called the anon key, it ships inside every Supabase app in the world, and on its own it grants nothing — because row-level security is what decides what it can reach. An anon key on a properly configured database is a key to a locked door.

What you are actually checking for is the other one. Copy that long value and paste it into any JWT decoder, or just look at the middle section of it. If the decoded content says "role": "service_role" instead of "role": "anon", stop reading and treat it as an emergency: the service_role key bypasses row-level security entirely by design. Publishing it is equivalent to publishing your database password. It happens when an AI assistant, trying to make a stubborn query work, swaps one key for the other because the error goes away.

Clean result: the key in the browser decodes to anon.

Check 3: are your secrets in your git history?

If you exported your project to GitHub, one command answers this. In the project folder:

git log --all --oneline -- .env .env.local .env.production

If that prints nothing, no environment file was ever committed and you are fine. If it prints a list of commits, those files are in your repository's history — and deleting the file later did not remove them. Anyone who can read the repo can read the old versions, and if the repo was ever public, assume automated scanners found it within minutes. That is not paranoia; scanning GitHub for committed keys is an entirely automated industry.

The same applies to Stripe. A sk_live_ key belongs only in your hosting provider's environment variables, never in the code and never in the browser.

Clean result: the command returns nothing.

Check 4: is your storage bucket open?

Skip this if your app has no file uploads. If it does have them — profile pictures, document uploads, invoices — find one uploaded file's URL, copy it, and open it in an incognito window while logged out.

If it loads, that bucket is public. Sometimes that is correct: profile photos and logos are usually meant to be public. Sometimes it very much is not, and the pattern to look for is whether the URL is guessable. A file at /uploads/invoice-1024.pdf tells an outsider that invoice-1023.pdf probably exists too.

Clean result: private files return an error when requested without a session.

What to do if a check failed

Order matters here, and the instinct most people have is the wrong way round. Rotate first, fix second. A leaked key stays leaked after you fix the code that leaked it, because whoever copied it still has it.

So: rotate any key that appeared somewhere it shouldn't have, in Supabase and in Stripe. Then enable row-level security on every table that holds user data and write the policies. Then redeploy and re-run Check 1, logged out — because the only proof that a fix worked is a request that comes back empty.

That middle step is where honest advice has to admit its limits. Turning RLS on is one click in the Supabase dashboard. Writing policies that let your real users through while keeping everyone else out is genuinely fiddly, and the failure mode is not subtle: get it slightly wrong and your own customers get locked out, or you hit the error message that half of r/lovable is currently pasting into ChatGPT — infinite recursion detected in policy for relation. That one means a policy is asking a question that requires reading the very table the policy is protecting.

If you are stuck in a scanner loop where the AI reports everything fixed and the next scan disagrees, that is the signal to stop prompting. Every retry costs credits and the loop does not converge, because the model is editing configuration it cannot verify.

What these checks do not cover

I would rather be straight with you than have you close this page over-confident.

Four checks catch the flaws that are actually hurting people right now — the ones behind that CVE and behind most of the "I got hacked" posts. They do not cover authorization logic between different user roles, whether your API routes validate what they receive, whether one customer can access another's records by changing an ID in the URL, rate limiting, or whether you have backups that have ever been restored. Those need somebody to read the code.

But if all four came back clean, you are already ahead of most apps in this category, and the quiet voice can be told to stand down.

If you would rather not do this alone

If you ran the checks and found something, or ran them and are not certain what you were looking at, that is a completely reasonable place to be — none of this was ever explained to you.

I do a Production-Ready Audit for exactly this situation: every table's row-level security reviewed, keys and secrets checked and rotated, auth and payment flows tested for the gaps above, and a written fix list in priority order rather than a scanner dump. From $499, back in five to seven days. And if the checks above came back clean, I will tell you that and you keep your money.

You can also just send me a screenshot of whatever you found. I have looked at a lot of these and I will tell you whether it is serious, at no charge and with no sales pitch attached. Nobody should be losing sleep over a question this cheap to answer.

Follow-up questions

What people ask next

Is it safe to run these checks on my own app?

Yes. Every check below is a read-only look at what your own app already sends to your own browser, using developer tools that ship with Chrome. Nothing is installed and nothing is changed. Only run them on an app you own or have written permission to test.

Lovable's built-in security scan says my app is fine. Do I still need to do this?

Yes. A scanner reads your project's configuration; it cannot see what your live database actually returns to an anonymous request. The checks below test the real thing rather than the description of it, which is why they sometimes disagree with the scanner in both directions.

My app is only a prototype with no real users yet. Does this matter?

Not urgently, as long as no real personal data is in it. It matters the day you invite your first real user, and the fix is much cheaper before there is production data to migrate. Run the checks the week before you launch, not the week after.

I found a problem. Can I fix it myself?

Sometimes. Rotating keys and enabling row-level security on a table are both doable from the Supabase dashboard, and the order in this article is the order to do them in. Writing correct policies for an app with several user roles is where most people get stuck, because a policy that is slightly wrong locks out your real users instead of the attackers.

Production-Ready Audit

Every table's row-level security reviewed, keys checked and rotated, auth and payments tested — back as a written fix list in priority order.

From $499 · 5–7 days

Muhammad Bilal, Full Stack AI Developer

Muhammad Bilal

Full Stack AI Developer · Faisalabad, Pakistan

I build and rescue production AI SaaS products with Next.js, Supabase, Stripe and Claude. Most of my work is finishing apps that were started with Lovable, Bolt, Cursor or Replit and stalled somewhere between working and shippable.

SF
MS
BK
AS

5.0★ · 100% job success · 35+ projects delivered

All articles · RSS