AI rescue··8 min read

Vibe coding security checklist: what to test before launch

Before you launch an AI-built app, there are eight points worth checking for real. Here's how to test each one, what a pass means, and what to do if it fails.

Before you launch an app an AI tool wrote for you, there are eight points worth checking for real: server-side auth, database access, leaked keys, file uploads, payment webhooks, input validation, error logging, and backups. Each one ends one of two ways: it passes or it doesn't. Below, every failing point comes with the next step.

TL;DR

You can test all eight yourself in 30-40 minutes: server-side auth, RLS policy, leaked keys, upload limits, webhook signature, rate limiting, error tracking, and a real restore. Every section below has the exact command. If critical items still fail after this pass, you need someone to take over.

A generic security checklist doesn't know your setup. It doesn't ask which AI tool wrote the code, which database sits behind it, or whether a payment flow exists at all. This one is written specifically for AI-generated codebases: each item names what to test, how to test it, and the first move if it fails.

Auth: is the check in the browser or on the server

Test: skip the UI and call the app's protected API endpoint directly. Send a curl request with no session cookie or token, for example curl https://yourapp.com/api/orders. If real data comes back, the check only lives in the interface.

Pass: the server answers 401 or 403, no data returns. Fail: data comes back, and the "please log in" screen was only ever a visual gate. AI tools draw the form, the list, and the button correctly, but they tend to bury the permission check in the UI component instead of the request itself. Fix: verify the session server-side on every request, never trust the interface alone.

Checked in the UI only

Browser
↓ direct curl request
API
no token check, data returns

Checked on the server

Browser
↓ direct curl request
API
session verified, no token means 401

Database access: does the RLS policy actually hold

On a Supabase-backed project, the test is: connect as the anon role, run set local role anon;, then try select * from profiles;. A correct policy makes Postgres reject it with error 42501, permission denied, before the query even runs.

Pass: the anon role gets rejected, or only sees its own rows. Fail: the whole table comes back, which means RLS is off or the policy is missing. Fix: enable RLS on the table, write a policy that ties each row to auth.uid(), then run the test again.

Keys: which secret lives where

Test: pull your deployed build and scan the JS files that ship to the browser. Run this in your build output folder: grep -rEo "(sk_live_|sk_test_|service_role|whsec_)[A-Za-z0-9_]*" .next/static. That pattern catches the most common prefixes for Stripe secret keys, a Supabase service-role token, and webhook secrets.

Measured

Command: grep -rEo "(sk_live_|sk_test_|service_role|whsec_)[A-Za-z0-9_]*" .next/static

We ran this against lumiasoft.com's own production build. Result: no leaked key. The two matches it did surface came from our own security-check tool's UI copy, not an actual token.

Pass: no match, or the matches are your own interface text, same as ours above. Fail: a real token pattern shows up, something like sk_live_51... as an actual string. Fix: rotate the key immediately, move it to a server-side environment variable, run the grep again to confirm.

File upload: is type, size and access actually enforced

Test: upload a file with a mismatched extension, a text file renamed to .jpg. Then try one far bigger than expected, a few hundred megabytes. If the server accepts both, validation is only looking at the filename.

Pass: the server checks the real file signature, rejects the mismatch, and caps the size. Fail: both go through. Fix: verify the file's magic bytes server-side, enforce a size limit, and keep uploaded files out of the public web root.

Payment webhook: is the signature verified

Test: send a POST to your webhook endpoint with no Stripe-Signature header, or a fabricated one. If the server processes it anyway, there's no signature check.

Pass: the server returns 400 and rejects it. Stripe's official library verifies the signature using a timestamp plus HMAC-SHA256, and rejects anything outside a default five-minute tolerance too. Fail: the request gets processed regardless. Fix: add signature verification with the official library before any business logic runs.

Input validation and rate limiting: where does it break

Test: fire fifty requests at the same endpoint in quick succession. If none get blocked, there's no rate limiting. Also submit a value the form was never meant to accept, a negative price or a field far longer than expected.

Pass: requests start getting rejected with 429 past a threshold, and the form validates server-side too. Fail: everything goes through. Fix: add rate limiting, and repeat every client-side validation rule on the server. Never trust the client.

Logging: do you find out when it breaks

Test: trigger an error on purpose, send a malformed request or fill an invalid field. Then check your error tracker, Sentry or similar, or the server log itself.

Pass: the error shows up in your dashboard within a minute. Fail: the user sees an error and you see nothing. Fix: wire up an error tracker before launch, not after the first outage.

Backup and restore: have you tested data loss

Test: confirming a backup exists isn't enough. Actually restore one into a separate environment and see if the whole path works end to end.

Pass: the restore worked, and the data came back complete. Fail: nobody ever tried, or the restore stalls partway. Fix: turn on point-in-time recovery, and put a real restore drill on the calendar.

Pass/fail table: what order do you close these in

Once you've run all eight tests, you're left with a list. You don't have to close every item at once, but the order matters. Anything that touches user data directly comes first.

Server-side auth
Call the API with no token
Close first
RLS policy
set local role anon then select
Close first
Leaked keys
Grep the shipped JS bundle
Close first
Upload validation
Wrong extension, oversized file
Second
Webhook signature
POST with no signature header
Second
Rate limiting
Fifty requests in a burst
Third
Error tracking
Trigger an error on purpose
Third
Restore drill
Restore a backup for real
Third

What this checklist doesn't test

These eight are what to close first, not a full audit. This isn't a substitute for a real security review, a penetration test, or a compliance pass. Passing all eight doesn't mean every RLS policy covers every role, or that a business-logic flaw somewhere in checkout can't still cost you money. Manual tests catch what's visible from the outside. They don't replace someone reading the code.

If you'd rather move through these eight questions with guided answers instead of running each test by hand, our AI app launch-readiness security check asks the same categories in 11 questions and ranks the findings by severity. For more on where the RLS and leaked-key gaps come from in the first place, see what Lovable and Bolt apps leave exposed. If you haven't moved the project off Lovable yet, our export guide is the step before this one.

If the critical items on this list are still open and you can't close them yourself, our AI project completion service picks up exactly there.

Frequently asked questions

Which app builders does this checklist apply to?
Most apps built with Lovable, Bolt, v0, Cursor, Replit, or Base44 share a similar backend pattern, usually Supabase or something close to it. These eight items were written around that pattern.

How long does the full pass take?
Running all eight tests by hand takes 30-40 minutes. Most of the commands are no more than a terminal and a curl request.

What if I can't run any of this myself?
If you can't run the commands yourself, you need someone who can interpret the findings. Our tool's results screen explains each item in plain language, but closing a critical gap is still technical work.

Does RLS only apply to Supabase?
The term comes from Supabase, but the idea holds for any database: row-level access control. Firebase's security rules and policy-based access on a plain Postgres instance do the same job.

How do I test a restore without risking production?
Use a separate environment, not production. Restore the backup there, confirm the data is complete, then tear that environment down.

Need help with this?

Let's talk in a 45-min discovery call.

Book a call