Vibe Coding Security Checklist: 12 Checks Before You Ship
Vibe coding ships fast because you stop writing every line yourself. The tradeoff is that your AI assistant writes exactly what you didn't think to forbid — a hardcoded API key, an open database rule, an admin route with no check. This checklist is the counterweight. It walks a solo founder through 12 checks, in the order they leak on launch day, covering secrets, authentication, database rules, and headers. Every check ends with a prompt you can paste into Cursor, Claude Code, or Lovable, so you close the hole instead of just finding it.

Contents
- How to use this checklist
- Phase 1: Secrets
- 1. Give the agent security rules before it writes another line
- 2. Scan for hardcoded secrets before they reach Git
- 3. Keep server-only keys out of the browser
- 4. Check what's committed to Git and what the host serves
- Phase 2: Database
- 5. Turn on row-level security (Supabase) or lock the rules (Firebase)
- 6. Use the anon key in the client, never service_role
- 7. Lock down storage buckets
- Phase 3: The application
- 8. Add an authorization check to every API route
- 9. Fix the authentication basics
- 10. Set the security headers
- 11. Audit dependencies for known vulnerabilities
- Phase 4: Before you hit deploy
- 12. Run a black-box scan and read the proof
- Frequently asked questions
- How secure is vibe coding?
- What's the first thing I should check?
- Do I need to be a security expert to follow this?
- Should I use a security scanner for an AI-built app?
- How is this different from a penetration test?
- Ship it, then scan it again
- Sources
If you want to understand why these leaks happen before you start patching, read our explainer on vibe coding security risks. This article assumes you've already spotted a problem or two and want the fix list.
How to use this checklist
Work top to bottom. Each check has three parts: what to look for, how to verify it on your own site, and a prompt for your agent. You don't need to be a security engineer. If a check fails, paste the prompt and let the agent make the change. The prompts name files and settings where possible, and each one ends with a way to confirm the fix actually landed.
Phase 1: Secrets
Secrets are the fastest way to get burned, so they come first.
1. Give the agent security rules before it writes another line
Your assistant generates what you asked for, not what you assumed. If you never told it "no secrets in code" or "every table needs row-level security," it won't add those by itself. A short rules file fixes this at the source.
Add these rules to my CLAUDE.md (or Cursor rules) and apply them to every change:
- Never write secrets, keys, or tokens into code. Read them from environment variables.
- Never send server-only keys (service_role, Stripe secret, OpenAI secret) to the browser.
- Every data read or write must have an authorization check first.
- If the project uses Supabase, every table must have row-level security and a policy.
- An API route must never return more data than the current user is allowed to see.
Then check that the file exists in the project root and that new edits follow it.
This is the highest-leverage check on the list: it prevents most of the others.
2. Scan for hardcoded secrets before they reach Git
One leaked Stripe or OpenAI key turns into a surprise bill. One leaked service_role key turns into full database access. The time to catch them is before the commit, not after.
Run a secret scanner over your repo — gitleaks or trufflehog both work — or ask the agent to grep for common patterns:
Search the entire codebase for hardcoded secrets: Stripe keys, OpenAI keys, Supabase
service_role keys, AWS keys, and any value assigned to a variable named API_KEY, SECRET,
or TOKEN. List every file and line. Move each secret to an environment variable and
reference it by name, then check that the browser bundle no longer contains the raw value.
A free Rustine scan does the same thing from the outside: it checks what your deployed site actually serves and flags secrets that made it into the client bundle.
3. Keep server-only keys out of the browser
In Next.js, any environment variable prefixed with NEXT_PUBLIC_ is inlined into the JavaScript that ships to the browser. Vite uses VITE_, Nuxt uses NUXT_PUBLIC_. That prefix is the difference between a key that stays on the server and a key anyone can read by opening DevTools.
Only your public key belongs there — for Supabase, that's the anon key, which is safe to expose precisely because it respects row-level security. We cover that distinction in Supabase anon key: is it safe to expose in the browser?
Find every environment variable in the client bundle. List each NEXT_PUBLIC_ (or VITE_,
NUXT_PUBLIC_) variable and what it holds. For each one, confirm it is a public value,
not a secret. Rename any secret to drop the public prefix and read it only in server
code, then check that the value no longer appears in the built client JavaScript.
4. Check what's committed to Git and what the host serves
A .env file committed to the repo is a leak that outlives the moment you made the mistake. The same goes for a deployed .git folder, a secrets.json, or a production build that still ships source maps.
Check the repo and the deployed site for: a .env file or .env.example with real values,
a secrets.json or config file with credentials, a committed .git folder, and source maps
in the production build. Move real values to the host's secret manager, add these files
to .gitignore, and disable source maps for production. Then check that none of these
files are reachable at their public URL.

Phase 2: Database
This is where vibe-coded apps leak the most user data. The database is usually the most valuable thing you ship.
5. Turn on row-level security (Supabase) or lock the rules (Firebase)
Row-level security, or RLS, is the per-row permission layer in Supabase. A table with RLS disabled and your anon key exposed is readable by anyone who has the key — and the anon key is, by design, public. If you haven't written policies yet, see Supabase RLS: securing your AI-coded app.
On Firebase, the equivalent is your Firestore, Storage, and Realtime Database rules. A rule of allow read, write: if true; is public to everyone on the internet, not just your app.
List every Supabase table and its RLS status. Enable RLS on any table that is off, then
write a policy that lets users read and write only their own rows (for example, matching
auth.uid() to a user_id column). Then check that the Supabase dashboard shows no RLS
warning for any table and that an anonymous request can no longer list other users' rows.
For Firebase, swap the platform: replace RLS with Firestore and Storage rules, and require request.auth != null plus an ownership condition.
6. Use the anon key in the client, never service_role
The anon key respects RLS; the service_role key bypasses it entirely. If service_role appears anywhere in client code, treat it as a live leak — you need to revoke and rotate it, not just move it. The full explanation is in Supabase service role key: where it must never appear.
Search the client code for the service_role key or any use of the admin client. Replace
admin operations with authenticated user operations using the anon key and RLS policies.
If service_role ever shipped to the browser, rotate the key in the Supabase dashboard.
Then check that the client bundle contains only the anon key.
7. Lock down storage buckets
A public Supabase Storage bucket lets anyone list and download every file in it. Public is right only for buckets that hold truly public files, like avatars and product images — never for invoices, exports, or anything user-specific.
List every storage bucket and whether it is public or private. For each public bucket,
list what it holds and confirm the files are meant to be public. Make any bucket with
user-specific or sensitive files private, then check that an unauthenticated request can
no longer list or download those files.

Phase 3: The application
Once the database is locked down, the app itself is where the remaining leaks live.
8. Add an authorization check to every API route
The most common leak in AI-built apps is an API route that returns too much: a /api/users endpoint that sends back select * for every row, or an object reference without an ownership check. The second pattern has a name — IDOR, for insecure direct object reference. It means "I can read or change someone else's record just by changing the ID in the URL."
The rule is simple — before an endpoint returns data or makes a change, verify the current user is allowed to touch that specific record.
Audit every API route. For each one, confirm it verifies the current user is authenticated
and authorized for the specific record, not just logged in. Where a route returns rows
from a table, make sure it filters by the user's ID and never returns other users' data.
Then check that two different test accounts cannot read or modify each other's records.
9. Fix the authentication basics
This is the boring layer that AI assistants often skip: email confirmation, sign-up abuse, and cookie flags. Turn on email confirmation so nobody can register an address they don't own. Rate-limit sign-ups and logins so a script can't hammer your auth endpoints. Set cookies to httpOnly (not readable by JavaScript), SameSite (not sent on cross-site requests), and secure (only over HTTPS).
Review the authentication setup and confirm: email confirmation is required, sign-up and
login are rate-limited, and session cookies are httpOnly, SameSite, and secure in
production. Fix anything missing, then check that an unconfirmed email cannot access
authenticated pages and that cookies show the correct flags in the browser.
10. Set the security headers
Security headers are the browser's instruction manual. CSP — Content Security Policy — tells the browser which scripts it may load. HSTS forces HTTPS. frame-ancestors stops your site from being framed inside another page, which is how clickjacking works. A missing header is a fix that takes minutes.
Add or fix the security headers on the site: a Content-Security-Policy that allows only
your own scripts, a Strict-Transport-Security header, and a frame-ancestors directive
that blocks framing. Then check the response headers with a curl request or a header
checker and confirm each header is present with the expected value.
11. Audit dependencies for known vulnerabilities
Most of your app's code isn't yours — it's the libraries the assistant pulled in. A known vulnerability in one of them is a known vulnerability in your app. Run npm audit (or pnpm audit, yarn audit, pip-audit) and fix what it flags.
Run npm audit (or the equivalent for this project) and list every high- and
critical-severity finding. Update the affected packages to patched versions, re-run the
audit, then check that no high or critical findings remain.
Phase 4: Before you hit deploy
12. Run a black-box scan and read the proof
The final check is to stop looking at your code and look at your site the way a stranger would. A black-box scanner requests your app like an attacker and shows what leaks — with the request and the response as proof, not a guess. That catches the things you and the agent both forgot.
Run a free scan of your site at rustine.io. It checks the leaks above — exposed secrets, open database rules, missing headers, over-sharing endpoints — and hands you the proof plus the fix to paste back into your agent.
If you want a checklist tailored to the Supabase layer specifically, we've written one: Supabase security checklist: 10 checks before you launch.
Frequently asked questions
How secure is vibe coding?
As secure as the instructions you give the assistant. Vibe coding isn't inherently insecure, but the assistant writes the simplest code that satisfies the prompt, and "simplest" often means "no authorization check." The risk is real and concentrated in a few predictable places — secrets, database rules, and API routes — which is why a short checklist covers most of it.
What's the first thing I should check?
Secrets. They're the fastest to leak and the most expensive when they do. Run a secret scan (check 2), then confirm your server-only keys never reached the browser (check 3). Everything else can wait a day; a leaked key can't.
Do I need to be a security expert to follow this?
No. Each check has a prompt your coding agent can act on directly. The agent writes the fix; you verify it with the "then check that…" step at the end of each prompt.
Should I use a security scanner for an AI-built app?
Yes, for the final pass. A scanner sees your deployed site the way a stranger does and shows the proof — which is the one thing reading your own code can't give you. Use it as the last check, not the only check.
How is this different from a penetration test?
A pen test is a deep, manual review by a human, usually expensive and scheduled. This checklist is the pre-launch hygiene you can run today, in an afternoon, with the agent you already use. They complement each other: do this first, budget for a pen test later if you hold sensitive data.
Ship it, then scan it again
Work the list top to bottom, paste the prompts, and verify each fix. The point isn't a perfect security posture on day one — it's not shipping the obvious holes. When you're through the 12 checks, run a free scan at rustine.io to confirm what actually changed from the outside, and re-run the checklist after any major feature.