HomeChecklistsShip checklist for a vibe-coded app

Checklist

Ship checklist for a vibe-coded app

The ordered checks to run before you share the URL of an app built with Lovable, Bolt, Cursor, Supabase, or Firebase, from leaked keys to payments.

6 min read

Independent notes from VibeLocked. Not a pentest, and not affiliated with the tools named below.

You are about to send someone the URL. The app was built with an AI coding tool, it works in the preview, and the database has real rows in it, or it will by tonight. Run this list in order. Each item links to the longer note when you need the why.

This is not a pentest. It is the pass that catches the failures these apps actually ship with: a public key that was supposed to be a secret, a table with no rule, and a route that trusts the browser.

Once the app is live, the Live App Scanner checks the deployed URL from the outside, and the repo scanner checks a public GitHub repo. Neither replaces this checklist, but both catch the most common misses in under a minute.

1. Revoke anything that already leaked

If you find a live secret in git, in a screenshot, in a chat, or in the built JavaScript, stop and follow the first hour guide. Delete the file after the provider has revoked the key, not before.

If you do not find one, still search. The strings to look for are in the frontend secrets guide. For a public GitHub repo, also run through how to check if your API key leaked on GitHub.

2. Sort keys into public and secret

Public, and safe to ship only because something else enforces access:

Secret, server only:

  • Supabase secret key, or the legacy service_role key.
  • Firebase service account JSON and any Admin SDK credential.
  • Stripe secret key, restricted key, and webhook signing secret (whsec_).
  • Provider keys for OpenAI, Anthropic, AWS, and anything else that can spend money or read data.

A NEXT_PUBLIC_ or VITE_ prefix means the value is for the browser. A secret must not wear that prefix.

3. Lock the database the client can reach

Supabase: run the RLS checklist. RLS on, grants tightened, one policy per operation, views checked, storage policies written (Supabase Storage bucket security), upload routes validated (secure file uploads in Next.js), tests run as anon and authenticated (test Supabase RLS policies in CI). If you use Realtime, lock private channels too (Supabase Realtime authorization). Database triggers that call realtime.send or broadcast_changes need their own pass (Broadcast from the database).

Firebase: run the rules checklist. No test-mode rule, no if true (what if true really allows), owner checks on private documents, queries that match the rules, separate rules for Storage and Realtime Database.

If the generator used both, do both. They do not protect each other.

Also search the server for raw SQL escapes ($queryRawUnsafe, Drizzle sql.raw, string-built rpc filters). The SQL injection guide for Prisma, Drizzle, and Supabase shows the safe tagged-template patterns.

4. Check every way in, not just the pages

For each API route, Server Action, and Edge Function:

  • The caller is taken from the session, not from a userId in the body.
  • The query is limited to that caller.
  • Admin behavior checks a role the user cannot edit, on the server, not a flag in the browser (client-side auth is not security).
  • A Next.js page redirect is not the only check. Next.js says to verify Route Handlers and Server Actions on their own. Details are in the vulnerabilities guide.
  • Self-hosted Next.js is on a version patched for the middleware auth bypass, CVE-2025-29927: 15.2.3, 14.2.25, 13.5.9, 12.3.5, or newer.
  • Post-login redirects (callbackUrl, redirectTo, next, redirect_url) only allow same-origin relative paths. Test with ?callbackUrl=https://evil.example and confirm you stay on your app (open redirect guide).
  • Login, signup, and password reset are rate limited on the server, and a wrong password and an unknown email get the same message (login brute force protection).
  • Vercel Cron paths (/api/cron/...) reject requests without Authorization: Bearer matching CRON_SECRET. A bare browser GET must return 401 (cron guide).

Create two test users and request user A's id while signed in as user B. The IDOR fix guide for Next.js and Supabase has the browser and curl steps, and what a passing result looks like.

5. Payments

If you take money, the browser does not get to say that a payment succeeded. The Stripe webhook guide covers signature checks, the raw body, and duplicate events. Do not unlock a paywall from a success URL query string either; that is the Stripe success page paywall bypass. Use test keys until that route is verified. Switch to live keys only on the server, stored as environment variables, not in the repo. Stripe's best practices say not to put secret keys in source code.

6. Look at the built files

Build the app the way you deploy it. Search the output for secret prefixes. For Vite that is usually dist/. For Next.js it is usually .next/. For an Expo app, run npx expo export and search dist/ with grep -a, as the Expo and React Native API keys guide shows. A secret that appears there is already public, even if the source looks careful.

While you have the production build open, check its response headers. If there is no Content-Security-Policy, the CSP checklist has a starter policy and a Report-Only rollout that will not break checkout or sign-in. CSP is the backstop, not the fix: search the source for dangerouslySetInnerHTML, rehype-raw, and user-supplied href values, and sanitize or allow-list each one (XSS in React and Next.js).

In the same headers, look for Access-Control-Allow-Origin. If it echoes back any origin you send, follow the CORS misconfiguration guide. Also request /.env and /.git/config on the live domain; both should return 404 (how to check).

7. Ask the tool that wrote it, then read the diff

Use the review prompt. Reject any change that disables RLS, opens a Firebase rule, or moves a secret into the client to make a request succeed. Also confirm every new dependency the model added is a real package on the registry (AI hallucinated packages / slopsquatting). Vet any MCP server you connected to the coding tool the same way (MCP server security).

8. Share a staging URL first

Point the staging project at a separate Supabase project or Firebase project, with separate Stripe test keys. Do not debug against production data. On Vercel, check which variables reach preview deployments, because a public preview URL with production keys is production. When you are ready, deploy the rules and the server env vars in the same change as the code. A new client against old open rules, or old client code against a secret that was rotated, is how launches break.

9. Write down where the new keys live

You will rotate them again. Stripe recommends practicing rotation before you need it. A note that says which host holds STRIPE_SECRET_KEY and SUPABASE_SECRET_KEY is enough, for example Vercel, Supabase Edge Function secrets, or Replit Secrets. Do not put the values in that note.

After you ship

Turn on GitHub secret scanning and push protection if the code is on GitHub. Public repositories get secret scanning for free. The leak guide links the docs.

If the app was generated by Lovable, Bolt, v0, Cursor, or Replit, the pre-launch checklist has the tool-specific places keys tend to hide. Use it as the second pass, not as a different standard.

Official docs

Questions

What should I do first if I find a live secret in the repo?

Revoke or rotate the key at the provider before you delete the file. Removing it from the latest commit does not make the old value useless. GitHub's remediation guide says revocation is the step that matters.

Is this checklist a security audit?

No. It is a pre-launch pass you can run yourself. It will not find every bug. High-risk products still need a human review.

Which guide do I open for database rules?

Use the Supabase RLS checklist if the app uses Supabase, and the Firebase security rules checklist if it uses Firestore, Realtime Database, or Storage.