All posts
VibeZero Team4 min read

Lovable security checklist

Ten checks for a Lovable app, each one runnable in under two minutes, in the order that matters. Copy the command, read the answer, decide if you can launch.

  • security
  • vibe coding
  • lovable

Every item here is a check, not a task. You run a command or open a page, you read an answer, and you know something you did not know before. Nothing takes more than two minutes, and the order is deliberate: the first three decide whether your data is public right now, and everything after that is real but less urgent.

If you only have ten minutes, do 1 through 3 and stop.

The blocking three

1. Is Row Level Security on for every table? Supabase SQL editor:

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

Any row with rowsecurity = false is readable by anyone holding the anon key, and the anon key is in your JavaScript bundle. This is the failure behind CVE-2025-48757, which confirmed 170-plus exposed production apps. Pass: every table reads true.

2. Does the policy actually deny? Do not accept the presence of a policy as an answer. Attempt the read yourself, with nothing but the public key:

curl -s "https://<project-ref>.supabase.co/rest/v1/profiles?select=*" \
  -H "apikey: <your-anon-key>"

Pass: []. Rows coming back means that table is a public API endpoint.

3. Is a bypass credential in your bundle?

npm run build
grep -rEo "eyJ[A-Za-z0-9_-]{20,}|sb_secret_[A-Za-z0-9]{20,}|sk-[A-Za-z0-9]{20,}" dist/

Your anon key appearing is expected and fine once 1 and 2 pass. A service_role key or an sk- provider key appearing is not: the service role key carries BYPASSRLS and makes checks 1 and 2 irrelevant. Pass: nothing but the anon key. If something else appears, rotate it before you touch any code, because a key in a deployed bundle is already public.

The next four

4. Did a key leak earlier and get “removed”? The working tree looking clean says nothing. Git keeps every version of every file:

git log -p --all | grep -nEo "sk-[A-Za-z0-9]{20,}|eyJ[A-Za-z0-9_-]{20,}|ghp_[A-Za-z0-9]{36}"

Pass: no output. Any hit needs rotating at the provider, not deleting from the repo, for the reasons in why a leaked key stays leaked.

5. Can one user read another user’s record? Two accounts, one request. Take an id belonging to user A and fetch it with user B’s session:

curl -s -o /dev/null -w "%{http_code}\n" \
  "https://your-app.example/api/documents/<A-record-id>" \
  -H "Authorization: Bearer <user-B-token>"

Pass: 404 or 403. A 200 is an IDOR, the class OWASP ranks first, and it means the check exists nowhere in that path.

6. Can one user delete another user’s record? Same setup, write path. This is the one people skip, and the damage is larger:

curl -s -o /dev/null -w "%{http_code}\n" -X DELETE \
  "https://your-app.example/api/documents/<A-record-id>" \
  -H "Authorization: Bearer <user-B-token>"

Pass: 404 or 403.

7. Do your update policies have both clauses?

select tablename, policyname, cmd, qual, with_check
from pg_policies
where schemaname = 'public';

Pass: no true in qual or with_check on a table holding user data, and every UPDATE policy has both columns populated. An update policy with only using lets a user reassign a row they own to somebody else’s account, which is a write into another user’s data. The RLS explainer has the detail.

The last three

8. Are your storage buckets private? Open Storage in the Supabase dashboard and read the Public column, then confirm each private bucket has policies. A public bucket serves every object to anyone with the URL, which is precisely how the Tea app exposed 72,000 photos including 13,000 government IDs. Pass: no bucket is public unless it holds content you would post on your homepage.

9. Are you shipping known-vulnerable dependencies?

npm audit --omit=dev

Pass: zero critical and high. Generated manifests reach for the versions the model remembers rather than the current patched release, so a new project can start life already vulnerable, and nothing bumps the lockfile afterwards.

10. Does an unauthenticated request to your Edge Functions get rejected?

curl -s -o /dev/null -w "%{http_code}\n" -X POST \
  "https://<project-ref>.supabase.co/functions/v1/<your-function>" \
  -H "Content-Type: application/json" -d '{}'

Pass: 401. A function that answers without a session and calls a paid API is somebody else’s free credits, and one that touches your database without a session is an open door with a longer URL.

What passing all ten does and does not mean

It means the failures that actually get Lovable apps breached are closed, and that is worth a lot: nine of these ten map to something with a CVE, an advisory, or a documented incident behind it.

It does not mean your app is secure, and the difference is the honest limit of any checklist. Nothing above reads your code. It cannot see a SQL query built by string concatenation in a function you have not opened, a webhook that never verifies its signature, an auth flow where a multi-step gate turned out to be skippable, or the common vulnerability classes that only appear when something reads the whole repository at once.

That is the gap between a checklist and a scan. The four fixes close what this list finds, and Lovable security covers what reading the repository across five layers reaches that ten commands cannot. Both are better than the third option, which is trusting the tool that wrote the code to grade it.

ShareXLinkedIn