What you need and what you will have at the end
You need your repository, your hosting dashboard, your database or backend console, two test accounts with different roles or owners, and a terminal with curl, git and your package manager. Run everything on your own project and your own test data. If you find a problem in someone else's app, tell its owner privately and do not probe it.
Copy the tables below into a document and fill in a result and the date for every row. The finished document is your evidence that the checks happened, and it makes the next launch faster.
Phase 1: Several days before launch, secrets and repository
Start here because a leaked key is the most expensive mistake and the slowest to clean up. The detailed methods are in our guide on exposed API keys, and the rows below are the launch subset.
| Check | Command or click path | Pass condition |
|---|---|---|
| No secrets in source | grep -rEn "sk_live_|service_role|BEGIN (RSA |EC )?PRIVATE KEY|postgres(ql)?://" . --exclude-dir=node_modules --exclude-dir=.git | No hit is a real credential |
| Env files untracked | git ls-files | grep -E '(^|/)\.env' | No output |
| No secrets in the build | npm run build, then grep the .next/static or dist folder for the same patterns | No matches |
| History clean or keys revoked | git log --all --oneline -S'sk_live_' | No hit, or every hit's key has been revoked |
| Keys pasted into chats rotated | Provider dashboard, API keys page, expire and replace | Old key returns an authentication error |
| Spending limits set | Each paid provider's billing or usage settings | A limit or alert exists for every paid API |
Phase 2: The day before, database and storage
Browser apps that talk straight to a managed database depend on its rules. Use the section that matches your stack and skip the other. The Supabase and Firebase guides on this site have the full procedure and fixes.
| Check | Command or click path | Pass condition |
|---|---|---|
| Supabase: RLS on everywhere | SQL editor: select tablename, rowsecurity from pg_tables where schemaname = 'public' | Every row is true, or a false row has a written reason |
| Supabase: anonymous read blocked | curl the REST URL for each table with only the publishable key | Empty array or permission error |
| Supabase: buckets private | SQL editor: select id, public from storage.buckets | Buckets holding user files show public = false |
| Firebase: no open rules | Console, Firestore, Storage and Realtime Database rules tabs, search for if true | None found |
| Firebase: signed-out refused | Rules Playground, simulate an unauthenticated read | Denied |
| Cross-user isolation | Sign in as test user B, request user A's record by id | No data returned and no change made |
Phase 3: The day before, access control and abuse
Broken access control means a signed-in user doing something they should not be able to do, and it is the flaw AI tools produce most often because a hidden button looks like a rule in the preview. Your test accounts are the way to prove otherwise. Use the browser's network tab to copy a real request, then replay it with the other account's session.
| Check | Command or click path | Pass condition |
|---|---|---|
| Admin routes enforced on the server | As a normal user, request an admin API route directly with curl using that user's token | 403 or 401, never data |
| Ownership checked on changes | As user B, send an update or delete for user A's record id | Rejected, and the record is unchanged |
| Paid routes require sign-in | curl your AI or payment route with no session | 401 response |
| Rate limiting on login and paid routes | Send repeated failed logins from a script against your own login endpoint | You are eventually refused, for example with a 429 |
| Password reset and email confirmation on | Auth provider settings, then run both flows with a test address | Both emails arrive and both links work once |
| Server-side validation | Send a malformed body directly to a write route with curl | 400 response, no record created |
| Errors leak nothing | Trigger an error on purpose in production or preview | No stack trace, SQL or internal URL in the response |
Phase 4: Launch morning, hosting and dependencies
Now the deployment itself. Headers are cheap to add, and a dependency audit takes minutes. Skip neither because they are boring.
| Check | Command or click path | Pass condition |
|---|---|---|
| HTTPS only with HSTS | curl -sI http://your-app.example, then curl -sI https://your-app.example | grep -i strict-transport | HTTP redirects to HTTPS and HSTS is present |
| Baseline headers | curl -sI https://your-app.example | grep -iE 'x-content-type|referrer-policy|content-security' | nosniff, a referrer policy and a frame-ancestors CSP are present |
| Debug and preview endpoints gone | Search routes and config for debug, test and seed paths, then request each on production | 404 or 401 for all |
| Test accounts and seed data removed | Admin or database console, filter by test emails and sample rows | None left, or each is disabled and unprivileged |
| Dependency audit | npm audit --omit=dev --audit-level=high | Exit code 0, or every remaining finding is read and accepted in writing |
| Production env correct | Hosting dashboard, environment variables for the production scope | Production keys are set, test keys are not, no secret has a public prefix |
Phase 5: At go-live, production smoke test
Once the production deployment is live, run one short pass as an outsider and one as a real user. This catches configuration that only exists in production, such as a missing environment variable or a rule that was deployed to staging only.
- 1Open the site in a private window with no session. Confirm the pages that should be public load and the pages that should not redirect to sign-in.
- 2Create a fresh account with a real email address. Confirm the confirmation email arrives, the link works once, and a second use of the link fails.
- 3Complete the main action of your app, including a payment in test mode if you take payments. Confirm the record is created under the right owner.
- 4Sign in as the other test account and confirm you cannot see or change the first account's record.
- 5Open developer tools on the live site and search all sources for the secret patterns from Phase 1.
- 6Read your provider dashboards for the first few live requests and confirm they use production keys and limits.
Phase 6: The first hours after launch
Keep the runbook open. Watch your hosting logs and each provider's usage page for spikes and for errors you did not expect, because a leaked key or an open endpoint shows up first as unusual usage. Have the rotation steps for every key within reach, and know who on the team can revoke each one.
If something fails, revoke first and investigate second, then write the finding into the same table so it becomes a permanent row.
Common mistakes
- Testing with your own admin account only, so every access check passes.
- Running the checks on a preview deployment and assuming production has the same settings.
- Skipping the second test account because creating one is tedious. It is the only way to test isolation.
- Fixing the failed rows and not re-running the passed ones. A fix in one area can undo another.
- Treating the dependency audit as noise. Read the high-severity results for packages that ship to production.
- Leaving the filled-in table undone, then having no record of what was verified.
How to verify
The runbook passes when every row in Phases 1 to 4 has a recorded result and date, every failed row is either fixed and re-run or has a written exception, and the Phase 5 smoke test completed against the live site. Keep the document with your release notes.
Keep it working
Re-run Phase 1 whenever someone pastes a key into a chat with an AI tool or a teammate leaves. Re-run Phase 2 after any migration or rules change. Re-run Phases 3 and 4 before every release that adds a route, a role or a third-party service. Store the tables in your repository and treat each launch as an update to them.
Frequently asked questions
How long should a pre-launch security pass take?
For a small app, most of it is copying commands and reading results, and the slow parts are creating a second test account and reading dependency findings. Budget a focused block, then keep the filled-in table so the next launch only needs re-runs.
Which phase matters most if I am short on time?
Secrets and data rules, meaning Phases 1 and 2. A leaked secret key or a table without protection exposes everything at once, while the other phases limit narrower problems. Do those two first, then access control.
Do I need a security professional before launching?
Not necessarily for a small first release. This runbook covers the most common gaps in AI-built apps. If you handle payments, health data or other sensitive records, or hold regulated data, get a qualified review, and check the rules that apply to you.
Can I run these checks against a competitor or a friend's app?
No. Only test systems you own or have written permission to test. Probing someone else's app can be illegal even with good intentions. If you notice a problem in another app, report it privately to its owner.