// GUIDE
The complete security checklist for AI-built apps
Updated 2026-08-07 · 11 min read
Work through these eight areas in order, database access, secrets and API keys, authentication, payments and webhooks, storage buckets, CORS and headers, dependencies, and git history. For each item, this guide tells you what to check, how to tell if it is wrong, and the direction of the fix. You do not need a security background; you need about an hour and the willingness to open your project settings and your browser network tab.
How to use this checklist
AI coding tools like Lovable, Bolt, Cursor, Replit and v0 write code that works on the first try, which is exactly why security gaps hide so well. Nothing looks broken. The app loads, sign-up works, payments go through. The holes only show up when someone opens the browser's network tab or points a script at your database.
This checklist is ordered by blast radius: the earlier items are the ones that leak the most data or cost the most money when they go wrong. Do them top to bottom. For each item you will (1) check one specific thing, (2) look for one specific warning sign, and (3) move in one clear fix direction. If you get stuck on the fix, that is normal, knowing the hole exists is most of the battle, and a scan can hand you the exact code or SQL to paste.
This is a black-box checklist you can run against a live app without touching the codebase. A connected (white-box) scan of the repo will catch more, but everything here is checkable by hand.
The checklist
- Database access (Supabase RLS / Firebase rules). Check: is Row Level Security ON for every table in Supabase, or are your Firestore/RTDB rules anything other than "allow read, write: if false" by default? Wrong if: RLS is off, or a policy uses "using (true)", or Firebase rules say "allow read, write: if true". How to tell: in Supabase, the Table Editor flags tables with RLS disabled; you can also hit your REST endpoint with just the anon key and see if rows come back. Fix direction: turn RLS on for every table and add a policy that ties each row to "auth.uid() = user_id"; in Firebase, scope every rule to "request.auth != null" and to the owner.
- Secrets and API keys. Check: which keys are reachable from the browser? Wrong if: any secret key (service_role, a Stripe "sk_" key, an OpenAI key, an SMTP password) appears in your frontend code, in a variable prefixed "NEXT_PUBLIC_", or in the built JavaScript bundle. How to tell: open your live site, view source / open the network tab, and search the JS for "sk_", "service_role", and "secret". Fix direction: any secret that shows up must be rotated (regenerated) and moved to server-side environment variables or an edge/serverless function; only publishable keys (Stripe "pk_", Supabase "anon") belong in the browser.
- Authentication and access control. Check: are the checks that decide "can this user see or do this?" running on the server, or in the browser? Wrong if: an admin page or a paid feature is hidden only by frontend code (a hidden button, a redirect, a conditional render). How to tell: log in as a normal user and try to reach a protected URL directly, or tamper with the request in the network tab. Fix direction: enforce every permission on the server (RLS, Firebase rules, or a check inside your API route), never trust the browser to police itself.
- Payments and webhooks (Stripe / Polar). Check: does your payment webhook verify the signature of every incoming event? Wrong if: your webhook route accepts any POST and grants access or credits without verifying the "stripe-signature" (or provider) header against your signing secret. How to tell: look at your webhook handler for a "constructEvent" / signature-verification step; if it is missing, anyone who finds the URL can fake a "payment succeeded" event. Fix direction: verify the signature with your webhook signing secret before trusting the event, and confirm the amount and currency match what you expect.
- Storage buckets (Supabase Storage / Firebase Storage). Check: are your file buckets public, and are upload/download rules scoped to the owner? Wrong if: a bucket is set to public and holds anything private (IDs, invoices, user uploads), or storage rules allow any authenticated user to read any file. How to tell: try opening a stored file URL in an incognito window with no login. Fix direction: make private buckets private, serve files through signed URLs, and scope storage policies to the file owner.
- CORS and security headers. Check: does your API allow requests from any origin? Wrong if: responses send "Access-Control-Allow-Origin: *" on authenticated endpoints, or you are missing basic headers. How to tell: inspect an API response in the network tab and look at the "access-control-allow-origin" value. Fix direction: restrict CORS to your own domain(s), and add standard headers (HSTS, X-Content-Type-Options, a Content-Security-Policy), most hosts let you set these in one config file.
- Dependencies. Check: are your packages up to date and free of known-vulnerable versions? Wrong if: "npm audit" reports high or critical issues, or your lockfile is months stale. How to tell: run "npm audit" (or "pnpm audit") in your project. Fix direction: update the flagged packages, prioritising anything in your auth, payments, or server code; re-run until high/critical are clear.
- Git history and committed files. Check: did a real ".env" file, a private key, or a credentials file ever get committed? Wrong if: your commit history contains an ".env" with real values, a service-account JSON, or a key, even if you later deleted it. How to tell: search your history ("git log -p -S sk_" or scan the repo with a secret scanner); GitHub also emails you secret-scanning alerts. Fix direction: rotate every exposed secret first (deleting the file does not un-leak it), then purge it from history and make sure ".env" is in ".gitignore".
Database access is where most of the damage happens
If you only fix one thing, fix this one. In a Supabase app, your database is reachable over the internet with the public "anon" key that ships in your frontend, that is by design, and it is safe only when Row Level Security (RLS) is turned on and every table has a policy. With RLS off (or a policy of "using (true)"), the anon key can read and write every row in the table. Firebase has the same shape: open rules ("allow read, write: if true") mean anyone can read your whole database.
The reason this bites AI-built apps specifically is that the fastest way to make a feature "work" during building is to loosen the rules, and AI tools, or the founder following their suggestion, often do exactly that and never tighten them back up. The app keeps working, so nothing signals that the front door is open.
If you take payments or store any personal data, treat an RLS/rules failure as an emergency, not a to-do, this is the item most likely to expose customer records.
The five-minute browser test anyone can run
You can catch several of these holes without reading any code. On your live site: open your browser, right-click, choose "Inspect", and go to the "Network" tab. Then use the app normally, sign up, log in, load your dashboard.
Watch the requests. If you see your database responding with rows of data to a request that only carries the public key, check whether it is returning other people's data too. Search the loaded JavaScript (the "Sources" tab) for "sk_", "service_role", and "secret", none of those should ever appear. Try opening a stored file link in a private window. Try visiting an admin or paid URL as a logged-out or basic user. Each of these maps directly to a checklist item above.
What to do after you find something
Do not panic and do not try to fix everything at once. Work in this order: first rotate any exposed secret (an exposed key stays dangerous until it is regenerated), then close database and storage access, then move auth and payment checks to the server, then handle CORS, dependencies, and git history.
Write down what you found and what you changed. When you re-test, use the same five-minute browser test, if the rows stop coming back, the keys are gone from the bundle, and the protected URLs bounce you, you have made real progress. A scan is the fastest way to both find the remaining items and get the exact fix for each one.
Key takeaways
- Work the eight areas in order of blast radius: database, secrets, auth, payments, storage, CORS/headers, dependencies, git history.
- Database access (Supabase RLS / Firebase rules) is the highest-stakes item, an open database exposes every customer record.
- The public "anon"/publishable key in your browser is safe only when Row Level Security or Firebase rules are correctly locked down.
- Every permission and payment check must run on the server; anything enforced only in the browser can be bypassed.
- A five-minute browser network-tab test catches exposed keys, open databases, public files, and client-side-only auth without reading code.
- An exposed secret stays dangerous until you rotate it, deleting the file or the commit does not undo the leak.
Frequently asked
Do I need to be able to code to use this checklist?
No. Every item has a check you can run from your browser or your project settings, and a fix direction described in plain English. You will need help (or a scan that hands you the code) to apply some fixes, but you can find every issue yourself.
How long should this take?
Budget about an hour for a first pass on a small app. The browser tests take five minutes; the database and payments items take the most time because they matter the most.
My app has been live for months, is it too late to check?
No, and it is more important, not less. If a hole has been open while you had real users, checking now tells you what to fix and, in the worst cases, whether you need to rotate keys or notify customers. Sooner is always better than later.
What is the single most important item?
Database access. Supabase RLS turned off, or Firebase rules left open, is the fastest way to leak every customer record, start there.