The pre-launch security checklist for vibe-coded apps
5 February 20269 min read
Vibe-coding tools are genuinely great at getting a working product out fast. They are not great, by default, at the unglamorous security work that stops that product from leaking user data on day two. This is the checklist we run before any project goes from 'built' to 'shared publicly.'
1. Row-level security on every table
If you're using a hosted Postgres/Supabase-style backend, row-level security (RLS) is what stops user A from reading user B's data by changing an ID in a network request. Check every single table — not just the obvious ones. It's common to lock down the 'profiles' table and forget a 'notes' or 'files' table added later. A five-minute audit: list every table, and for each one, confirm there's a policy that restricts rows to their owner.
- Every table has RLS enabled (not just policies written — actually enabled)
- Policies check auth.uid() against an owner column, not a client-supplied value
- Public tables (if any) are intentionally public, not accidentally open
2. Roles live in their own table
Never store an 'is_admin' or 'role' column on the same table a user can update about themselves. If a user can edit their own profile row, and that row also declares their role, you've built a self-service admin panel. Put roles in a separate table that only a security-definer function can check, and make sure no client-side update path touches it.
3. Secrets never ship in client code
Search your entire codebase for anything that looks like a service-role key, a secret API key, or an admin token, and confirm none of it appears in a file that ends up in the browser bundle. A good test: build your app for production, then grep the output folder for the word 'secret' or the first eight characters of any key you have. If it shows up, it's exposed to anyone who opens devtools.
4. Rate limit anything public
Contact forms, waitlist signups, public API endpoints — anything reachable without login needs a rate limit, or it becomes a free spam cannon or a way to run up your database bill. A simple per-IP limit (e.g. 3 submissions per 10 minutes) stops the overwhelming majority of abuse without needing a CAPTCHA.
5. Validate and sanitize on the server, not just the client
Client-side form validation is a UX feature, not a security control — anyone can call your API directly and skip your React form entirely. Every field that reaches your database should be re-validated server-side: length caps, type checks, and stripping anything that looks like markup if the field is ever rendered back as HTML.
6. Payments: never touch card numbers
Use a processor's hosted checkout or client-side tokenization so raw card numbers never pass through your own server. If your code ever has a variable holding a full card number, stop and redo that flow — it's both a security risk and often a compliance violation.
7. Have a plan for the report you hope you never get
Publish a security contact (even just an email like security@yourapp.com) somewhere findable. Researchers who find a real issue will often report it responsibly if there's an obvious way to reach you — and go public immediately if there isn't.
None of this is exotic. It's a checklist you run once before launch and revisit whenever you add a new table, endpoint, or payment flow. Velora's built-in launch checklist tracks exactly these items under its Security category, with the why and the how attached to each one.
Run the numbers on your own funnel
Funnel Doctor is free, takes four numbers, and tells you which stage is costing you the most.
Diagnose my funnel