The pre-launch security checklist for apps built with AI
The eight things that break AI-built apps once real people arrive — and how to check each one before they do.
If you built something with Cursor, Claude, Lovable, v0, Bolt or Replit and you are about to put it in front of real people, this is what to look at. Eight categories, in the order they tend to hurt. None of it assumes you are a security engineer.
01Your secret keys are probably in the browser
The single most common leak in AI-built apps, and the cruellest, because the name reads like it is protected. Any environment variable prefixed NEXT_PUBLIC_, VITE_ or REACT_APP_ is compiled into the JavaScript every visitor downloads. An AI assistant will happily add that prefix to make an error go away, and the app starts working, which is exactly why nobody looks again.
- Open your deployed site, view source, and search the bundle for the first six characters of each key you hold.
- Move anything secret behind an API route or a server action, so the browser asks your server and never holds the key.
- Rotate any key that was ever public. A key in a shipped bundle is a key someone already has.
- Publishable keys are fine — Stripe's
pk_key is designed to be public. Secret keys, database URLs and provider tokens are not.
02Your database may be open to the internet
Managed databases are reachable from anywhere by default; what stops a stranger reading your tables is a policy you have to write. Supabase and Firebase both ship this way, and the app works perfectly whether or not the policy exists — you cannot tell by using it.
- Turn on row-level security for every table that holds user data, then write a policy for each one. A table with RLS enabled and no policy denies everyone, which is the safe direction to fail.
- Check your storage buckets separately. They have their own rules and default to public far more often than people expect.
- Try to read another account's row while signed in as your own. If it works, so does it for anyone.
- Never ship the service-role key to the browser. It bypasses every policy you just wrote.
03Being signed in is not the same as being allowed
This is the failure AI-generated code makes most reliably. The route checks that you are logged in, then trusts the id in the URL. Change /invoices/1042 to /invoices/1043 and you are reading someone else's invoice — the login check passed, and it was never the question.
- For every route that takes an id, ask: does this confirm the record belongs to the person asking?
- Scope queries by the account, not just by the id — filter on both.
- Check the same thing on write and delete paths, not only on read. They are easier to forget and worse to get wrong.
- Do not rely on the interface hiding a button. Anyone can call the endpoint directly.
04AI features can spend your money without limit
A model call with no token ceiling generates until the model decides to stop, and you pay for all of it. Add a public endpoint and a retry loop and the bill is no longer bounded by anything you control. People discover this from an invoice.
- Set a maximum token count on every model call. There is no sensible default here.
- Put a per-user limit on any endpoint that calls a model, and require sign-in for the expensive ones.
- Cap retries. A loop that retries on failure will retry on a failure that repeats.
- Set a hard spend limit in your provider's dashboard as the last line of defence.
05Anything a user can type, a user can abuse
Rate limiting is what stops one person from making your service unusable for everyone else, and it is almost never in a first draft. Password reset and login are the two that matter first: without a limit, a script can try passwords all night.
- Limit login attempts, password resets and sign-up per address and per account.
- Limit anything that sends email or a text message. Those cost money and get your domain blocked.
- If users can upload files, cap the size and check what the bytes actually are — a filename proves nothing.
- Validate on the server. A check that only runs in the browser is a suggestion.
06The law reaches you from the first email address you store
Privacy rules are not a large-company problem. In the EU, the UK and California they apply from your first user, and app stores reject submissions without a policy. This is also the cheapest category to fix, because most of it is writing rather than engineering.
- Publish a privacy policy that says what you collect, why, who else receives it, and how someone deletes it.
- Publish terms. They are what let you close an abusive account and cap what you owe when something breaks.
- Make deletion real. If a user asks to be deleted, their rows have to go — including the copies in your analytics and your backups policy.
- Put a monitored address on the site. A user with a billing problem and nowhere to write disputes the charge instead.
07It works with ten users. That is not evidence.
Most AI-written database code fetches everything and filters afterwards. That is instant with fifty rows and a timeout with fifty thousand — and nothing in the code changed between the two. The same is true of a missing index: invisible, then suddenly fatal.
- Find the queries with no limit and no pagination. Those are the ones that fall over first.
- Index the columns you filter and join on. Postgres does not index foreign keys for you.
- On a serverless host, route through a connection pooler or you will exhaust the database's connection limit.
- Know what happens on deploy: whether requests in flight are lost, and whether a failed migration can be undone.
08If it breaks, will you find out?
The difference between a small incident and a bad one is usually how long it ran before anyone noticed. A new app typically has no error reporting at all, which means the first report comes from a user, if it comes.
- Add error tracking before launch, not after the first outage.
- Make sure errors reach you somewhere you actually read.
- Do not show the raw error to the user — provider errors name your model, your organisation and your limits. Show a reference, log the detail.
- Publish a security contact. A researcher who cannot reach you posts publicly instead.
09Or have it checked for you
Everything above can be checked by hand, and the list is worth reading either way. Launchprep does the deterministic half automatically — it reads your code, works out what you actually built, and runs only the checks that can apply to it.
npx launchprep
Free, unlimited, and it runs on your machine — nothing is uploaded. See what it covers.
Run every check that applies to your project, on your machine, free and unlimited:
npx launchprep