Skip to content
LockShip

Guide · Lovable · Updated

Is my Lovable app secure? Seven checks you can do yourself

If you are asking this, you have probably shipped something with Lovable, people are starting to sign up, and a quiet voice keeps asking whether their data is really safe. Listen to that voice. Nobody can promise you an app is secure, us included. What you can do is check the few places where Lovable apps most often go wrong, and you can do most of it yourself, with your live app in one tab and this page in the other.

The honest short answer

Lovable gives you a reasonable starting point. Its docs say it sets up row level security rules automatically when it builds features that store user data, runs a Quick security scan every time you publish, and steers API keys you paste into the chat into its Secrets store instead of your code. When the security firm Escape studied thousands of public vibe-coded apps, its report said plainly that Lovable “configures Supabase in the standard, secure way, just as any competent developer would.”

The trouble lives in the details that are specific to your app. A rule that lets everyone through. A table added later in a hurry. A backend function that never checks who is calling. Lovable is upfront about this too: its security overview says the scans “do not replace a thorough security review” and that you are responsible for your app meeting the security your use case needs.

Why so many people are asking

In 2025 one of these details got a CVE number. CVE-2025-48757 describes Lovable-generated sites, through 15 April 2025, where insufficient row level security let unauthenticated visitors read or write database tables. The NVD entry shows a critical 9.3 score from the CVE’s assigner, and it also records that Lovable disputes the CVE, on the grounds that each customer is responsible for protecting their own app’s data. The researcher, Matt Palmer, found the issue in March 2025 and published his write-up at the end of May.

Later that year Escape published research on more than 5,600 publicly available vibe-coded apps, most of them built with Lovable. They reported more than 2,000 vulnerabilities, over 400 exposed secrets, and 175 cases of exposed personal data, including medical records and IBANs. Their full report says nearly 60% of the apps had critical security flaws, and that the main cause was misconfigured permissions, especially Supabase row level security, rather than classic injection bugs. Escape is careful to say the sample leans heavily toward Lovable apps and is a snapshot in time, so read the numbers as “this is common”, not as a score for any one app.

We are not telling you this to scare you. We are telling you because the fixes are mostly small, and the checks below are the ones that would have caught those cases.

Two things to know before you start

There are two kinds of keys. Your app’s page carries a public key so the browser can talk to the database. On newer projects it starts with sb_publishable_, on older ones it is a long string starting with eyJ called the anon key. According to Supabase’s API keys guide, that key is meant to be public and only reaches what row level security allows. The other kind, the secret key (sb_secret_) or the older service_role key, bypasses row level security entirely and must never reach a browser.

Your backend lives in one of two places. Newer Lovable projects often use Lovable’s built-in backend, called Lovable Cloud, which Lovable says is built on Supabase’s open-source foundation. Others connect their own Supabase project. The checks are identical. Where you click differs, so each check below shows both.

The seven checks

Do them in order. The first takes a few minutes and might do half the work for you. Only test an app you own.

  1. Start with Lovable’s own Security view
  2. See which key your page sends
  3. Search your page for other secrets
  4. Ask your tables for data as a stranger
  5. Make your Edge Functions ask who is calling
  6. Look at your storage buckets
  7. Read your response headers

01

Start with Lovable’s own Security view

Open your project and go to More → Security. Lovable has two scans there. The Quick scan already runs every time you publish and looks at your database rules (tables without per-record access control, rules that let everyone through, leaked-password protection turned off), your npm dependencies, and any MCP server your app exposes without sign-in. The Deep scan reads your whole codebase for things like users reaching data that is not theirs, endpoints anyone can trigger, and leaked secrets. Lovable’s docs say running either scan is free.

Run the Deep scan, then read every Critical finding slowly before you click a fix. If one says leaked-password protection is off and your app has email sign-in, turn it on. On Cloud it lives in More → Cloud → Users → Auth settings → Email.

One caution from history. When Palmer wrote up the CVE, he noted that Lovable’s publish check would tell you whether RLS was enabled “but doesn’t necessarily indicate if they are sufficient.” The scans have grown a lot since then. Still, a scanner can see that a rule exists. It cannot know that your orders table should only ever show a customer their own orders. That part is yours, and it is what checks 4 and 5 are for.

02

See which key your page sends

Open your live app in Chrome, open DevTools, and go to the Network panel. Reload the page and use the app for a moment so it loads some data. In the filter box type rest/v1. Click one of the requests, and under Headers find the request header called apikey.

  • It starts with sb_publishable_: that is the public key. Good.
  • It starts with sb_secret_: that is a secret key. Supabase says secret keys are refused when they come from a browser, so your app is probably half broken already, but the key itself is now public. Go to the fix below.
  • It starts with eyJ: it is one of the older keys, and you need to know which. A JWT’s middle part is Base64-URL encoded JSON, and Supabase’s JWT docs explain that its role claim names the database role it acts as. Run this in the same DevTools window, in the Console tab:
Browser console
// Paste into the Console of your own browser. The key never leaves your machine.
const key = "PASTE_THE_KEY_HERE";
JSON.parse(atob(key.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))).role;
// "anon" is the public key. "service_role" means stop and read on.

Please decode it in your own console rather than pasting it into a website. If it ever turns out to be the secret one, you don’t want it in anyone else’s logs.

If you find a secret or service_role key in the page, fix the code first, then rotate the key. Ask Lovable to move whatever needs it into an Edge Function, and follow Supabase’s steps for a leaked key. Supabase also says it is deprecating the old anon and service_role keys by the end of 2026, so if your app still uses eyJ keys, moving to publishable and secret keys is worth putting on the list.

03

Search your page for other secrets

The Supabase key is not the only thing that can end up in the browser. Among the 400 or so secrets Escape found were GitHub tokens, OpenAI keys, and payment API keys. Lovable’s Secrets page draws the line clearly. Anything whose name starts with VITE_ is embedded into the code your visitors download. VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are fine to be public. Things like STRIPE_SECRET_KEY or OPENAI_API_KEY belong in Secrets, where Edge Functions can read them and the browser never sees them. Vite’s own docs say the same about its prefix: VITE_ variables should not contain sensitive information such as API keys.

To look for yourself, open DevTools on your live app and press Ctrl+Shift+F (Cmd+Option+F on a Mac). That opens the Search panel, which searches every file the page loaded. Try each of these in turn:

Search terms
sb_secret_
service_role
sk_live_
secret

Add the name of every service you have given a key to: Stripe, OpenAI, Resend, Google Maps, whatever you connected. A match is not automatically a leak. The word secret shows up in lots of harmless code. Click each result and read the line around it. If you see something that looks like a real key, treat it as public now. Revoke it at the service that issued it, make a new one, and ask Lovable to keep it in Secrets and call that service from an Edge Function.

04

Ask your tables for data as a stranger

This is the big one: the check behind the CVE, and behind the main cause Escape reported. Copy your project URL and the public key from check 2, open a terminal, and ask for one row the way any visitor to your site could:

Terminal
curl "https://YOUR-PROJECT.supabase.co/rest/v1/YOUR_TABLE?select=*&limit=1" \
  -H "apikey: YOUR_PUBLIC_KEY"

Do it for every table that holds anything about your users: profiles, orders, messages, invoices. Real rows from a table that should be private means anyone on the internet can read it right now. An empty list [] means the rules filtered everything out, or the table is empty. A permission error is also fine, if that is what you meant.

Then read the rules themselves.

On Lovable Cloud

More → Cloud → Database → RLS policies. Each rule shows what it applies to (SELECT, INSERT, UPDATE, DELETE), who it covers (anon or authenticated), and its expression. A rule for anon whose expression is simply true lets every stranger through. That view is read-only, so ask Lovable in chat to change a rule.

On your own Supabase

The Security Advisor in the Supabase dashboard flags public tables with RLS turned off. For the full picture, use the SQL audit queries in our RLS checklist, which list every table, whether RLS is on, and what a signed-out visitor may try.

Last, make two test accounts. Sign in as the second and try to open something that belongs to the first, for example by putting the first account’s record ID in the URL. You should see nothing of theirs. This is the test that catches a rule that is on but too loose, and no scanner can do it for you because only you know whose data is whose.

If you would rather have Lovable do the first pass, its best practices page suggests a prompt you can paste as is: “Review RLS policies to ensure users can only access their own data and shared data is properly protected.” Then test again yourself. For the reasoning behind each rule and SQL to fix them, the Supabase RLS checklist goes table by table.

05

Make your Edge Functions ask who is calling

When something can’t run in the browser, like taking a payment or calling an AI model, Lovable writes an Edge Function for it. Lovable’s security best practices make two points worth reading twice. A function running on the server can still be called directly, without going through your app, so being on the server doesn’t make it private. And code that uses the service role bypasses row level security, so that code has to check permissions itself.

Supabase’s own Edge Function docs carry the same warning in stronger words: a function that reads a shared table with the admin client and doesn’t filter by the caller’s ID “returns every user’s rows.”

Here is how to look without reading much code.

  • Open the code editor in Lovable and look for a file called supabase/config.toml. Supabase’s function configuration docs explain that a block like the one below lets anyone call that function without a valid token. That is right for a Stripe webhook, which should check Stripe’s signature instead. It is wrong for anything that acts on behalf of a user.
What to look for
# supabase/config.toml: each block like this turns off the sign-in check
[functions.stripe-webhook]
verify_jwt = false
  • The sign-in check that is on by default is not the whole answer either. Supabase notes that passing it doesn’t on its own prove who the caller is, so a function that does something for a user should look up the signed-in user first and refuse if there isn’t one. Lovable’s docs show exactly that pattern, returning 401 Unauthorized when no user comes back.
  • Then test it as a stranger. On Cloud, More → Cloud → Edge functions lists every function and has a Copy URL button. With your own Supabase, the URL is in the dashboard under Edge Functions. Call it with only the public key, the way someone who copied it from your page would:
Terminal
curl -i -X POST "https://YOUR-PROJECT.supabase.co/functions/v1/YOUR-FUNCTION" \
  -H "apikey: YOUR_PUBLIC_KEY" \
  -H "Authorization: Bearer YOUR_PUBLIC_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

A function that should only work for signed-in users ought to answer with a 401 or a similar refusal. If it happily sends an email, returns data, or spends your AI credits, ask Lovable to add a check that the caller is signed in and allowed to do this, and test again. The Deep scan from check 1 also looks for “functions and routes that anyone can trigger”, so compare notes.

06

Look at your storage buckets

Uploaded files live in buckets, and a bucket is either private or public. Lovable’s storage docs put the difference simply: private files are reachable only by users the rules allow, while public files are “accessible by anyone with the URL.” That is right for product photos and avatars. It is wrong for invoices, contracts, or ID scans.

On Lovable Cloud

More → Cloud → Storage. Each bucket card says Private or Public. Lovable blocks public buckets by default on every plan, so if you see a public one, find out why it is public and whether it should be.

On your own Supabase

Storage in the Supabase dashboard, or paste this into the SQL Editor:

SQL · buckets
select id, name, public from storage.buckets;

For private buckets, access comes from rules too. On Cloud, the RLS policies view has a Storage filter. With your own Supabase, step 8 of our RLS checklist has a policy that keeps each user inside their own folder.

07

Read your response headers

This one matters less than the database, and a missing header won’t leak your users’ data by itself. It is still worth five minutes. In the Network panel, reload, click the very first row (your page itself), and read the Response Headers. Look for:

  • Content-Security-Policy. MDN describes it as a set of instructions telling the browser what your page’s code may load and do, used mainly as a defence against cross-site scripting, where someone else’s script runs inside your app and sees what your users see.
  • Strict-Transport-Security, which tells browsers to stick to HTTPS for your site.
  • Either X-Frame-Options or a CSP that includes frame-ancestors. Without one, another site can load yours in a hidden frame and trick people into clicking. MDN suggests frame-ancestors 'none' unless you need your site to be embeddable.

If they are missing, ask Lovable how to add them for your project, then check this panel again. We couldn’t find custom response headers covered in Lovable’s hosting docs, so verify rather than assume. Older Lovable projects are client-rendered React and Vite apps (newer ones use TanStack Start), and for client-rendered apps MDN notes a CSP can also be set with a <meta http-equiv="Content-Security-Policy"> tag, though that route doesn’t support every feature. A strict policy can break things, so MDN’s advice to start with Content-Security-Policy-Report-Only, which reports problems without blocking anything, is a kind one.

What a clean result means

If you got through all seven and found nothing, that is genuinely good news, and you did more than most people do before they launch. It means nothing obvious is open from the outside today. It doesn’t mean the app is safe forever. New tables, new functions, and quick fixes at midnight have a way of undoing yesterday’s work. Lovable’s docs suggest reviewing your rules again whenever you change sign-in or your database, and that is good advice for all seven checks.

Where LockShip fits

We built LockShip because checks 2, 3, and 4 are easy to forget and tedious to repeat. You paste your app’s URL and prove you own the domain. The scan then reads your app the way a stranger would. It recognises a Lovable app, finds the Supabase URL and public key in your page, sees which tables that key can list, and makes read-only requests for at most one row from each. It looks through your JavaScript and any public source maps for secret keys, including a service_role key, notes public storage buckets, tries /.env and /.git, calls API paths it finds in your code without credentials, and reads your security headers, CORS, cookies, and certificate.

It is just as important to say what it doesn’t do. It never signs in as your users, so it can’t do the two-account test in check 4. It doesn’t test your storage rules or call your Supabase Edge Functions, and it never writes to your database. It is a point-in-time, best-effort check, not a penetration test, and a clean result means “nothing obvious from the outside right now”, not “safe”.

The summary is free: a score and the single worst thing we found. If you want the full report, the $39 Launch Safe certificate includes every finding with a fix prompt written for Lovable’s chat, the RLS SQL, a PDF, and seven days of re-scans while you fix things. A passing re-scan gives you a badge you can put on your site.

Want a second pair of eyes on checks 2 to 4?

Paste your Lovable app’s URL, prove you own the domain, and see what a stranger can read. The summary is free.

Run a free scan

Before you rely on a result, read the disclaimer.

Questions people ask

Is the Supabase key I can see in my Lovable app a leak?

Usually not. The publishable key (or the older anon key) is meant to be public. Supabase’s docs say it is safe to ship in a web page because it only reaches what Row Level Security allows, and Escape’s researchers found that the tokens Lovable exposes by default were these public anon tokens. The key that must never be in your page is the secret or service_role key.

Lovable’s security scan came back clean. Is my app secure?

It is a good sign, not a guarantee. Lovable’s own documentation says its scans help find common issues but do not replace a thorough security review and cannot guarantee complete security. A scan can tell you a rule exists. Only you know whether that rule matches who should see what in your app, so test it as a stranger and as a second user.

Was my app affected by CVE-2025-48757?

The CVE describes Lovable-generated sites through 15 April 2025 whose missing or weak Row Level Security let unauthenticated visitors read or write tables. Lovable disputes it, saying each customer is responsible for their own app’s data. Whatever the date of your project, the useful question is whether your tables are open today, and check 4 in this guide answers that in a few minutes.

I use Lovable Cloud, not my own Supabase project. Does this still apply?

Yes. Lovable says its built-in backend (Cloud) uses Supabase’s open-source foundation, and its data access is controlled by the same kind of row level security policies. The checks are the same. Only the place you click is different: Cloud has its own views under More → Cloud in the Lovable editor.

Sources

We checked each of these on 16 October 2026. Lovable and Supabase both change their products often, so if something here disagrees with their docs, trust the docs and tell us at hi@lockship.dev.