ShipWatchFree scan

Lovable app security check

Lovable gets you from idea to live app in an afternoon. This page is about the hour after that: checking that the app you shipped isn't handing your users' data to strangers.

The problem: the frontend is public, so the database rules have to do the work

A typical Lovable app is a React frontend that talks directly to a Supabase database from the browser. That design is fine, and it's fast. But it means two things are true at once:

When RLS is off, or the policies are too loose, anyone who copies your public key can query your tables directly, skipping every check your UI makes. Hiding a button in the interface doesn't protect the data behind it.

This isn't hypothetical. In 2025, Matt Palmer and Kody Low published a disclosure of insecure RLS configurations in Lovable-built apps, tracked as CVE-2025-48757. By scanning only the homepages of apps listed on Lovable's showcase, they reported 303 vulnerable endpoints across 170 of the 1,645 projects they analyzed. The exposed data included emails, payment details and API keys. They also showed a write: by removing the authorization header on one request they could insert a record marked "payment_status": "paid", skipping Stripe. (The NVD entry notes that Lovable disputes the CVE, on the grounds that each customer is responsible for protecting their own app's data. Either way, the job of checking falls to you.)

How to check your Lovable app yourself

Set aside 20 minutes. Do this on your own app only.

1. Check RLS on every table

Open your project in the Supabase dashboard and run this in the SQL editor:

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

Any row where rowsecurity is false is a table anyone with your public key may be able to read and write. Also open the Security Advisor in the dashboard; it flags tables in the public schema without RLS, along with other common mistakes.

2. Read the policies, not just the switch

RLS being "on" isn't enough. A policy like using (true) lets everyone in. The CVE write-up made the point that a check which only confirms a policy exists can give false comfort. For each table ask: who should be able to select, insert, update and delete, and does the policy say exactly that?

3. Try it as a stranger

Log out of your app, open developer tools, go to the Network tab and reload. Find a request to your-project.supabase.co/rest/v1/.... Copy it as cURL, delete the Authorization header, and change the query to select=*. If you get back other people's rows, a stranger can too.

4. Look for real secrets in the bundle

In developer tools, open Sources and search all files (Ctrl/Cmd+Shift+F) for sk_live_, sk-, service_role, sb_secret_ and eyJ. Long strings starting with eyJ are JWTs; a Supabase one whose payload says "role":"service_role" is a master key and must never be in the browser. See Supabase service_role key exposed.

5. Check the basics

curl -sI https://yourapp.com            # look for content-security-policy, strict-transport-security
curl -sI http://yourapp.com             # should be a 301/308 to https://
curl -s  https://yourapp.com/.env       # should NOT return KEY=value lines

How to fix it (paste these into Lovable)

Lovable works best with clear, specific instructions. Paste one prompt at a time and review what changes.

Audit every table in my Supabase database. For each table: enable Row Level Security, then write explicit policies for select, insert, update and delete. Users must only be able to read and change rows where user_id = auth.uid(). Use "to authenticated" and "(select auth.uid())" in the policies. Do not use "using (true)" on any table that holds user data. Give me a list of every table and the policies you created.
Find every place my app relies on the frontend to enforce permissions (hiding buttons, filtering lists, checking payment or admin status in React). Move each of those checks into the database with RLS policies or into a Supabase Edge Function, so the rule still holds if someone calls the API directly without the UI.
Payment status, credits, roles and admin flags must never be writable by the user. Make those columns read-only for the anon and authenticated roles, and only update them from a server-side Edge Function that verifies the Stripe webhook signature.
Search the whole codebase for API keys and secrets (Stripe secret keys, OpenAI keys, Supabase service_role or sb_secret keys). Move every one of them out of frontend code and into Supabase Edge Function secrets, and call those services from an Edge Function instead of from the browser. List what you moved so I can rotate those keys.

After any key leaves the frontend, rotate it in the provider's dashboard. Removing it from the code doesn't un-publish the old builds people already downloaded.

Then repeat the "try it as a stranger" test. If the request now returns an empty list or a permission error, the fix worked.

What the free ShipWatch scan does (and doesn't) check

ShipWatch fetches your public page and its JavaScript the way a browser does and looks for leaked secret keys (including Supabase service_role and secret keys), a public .env or .git folder, missing security headers and a missing HTTPS redirect. Each finding comes with a plain-English explanation and a fix prompt. If it sees a Supabase project it tells you, but it does not query your tables, so RLS is still something you verify with the steps above. For the full list, see the vibe coding security checklist.

Scan your app free in 30 seconds

Paste your live URL. ShipWatch checks your page and JavaScript for leaked keys (including Supabase service_role), exposed .env and .git, missing security headers and HTTPS redirects, and gives you a plain-English report with fix prompts. Passive and read-only, no signup.

Run the free scan