The problem with security bugs is that they don’t announce themselves. An app with no authorization checks works perfectly. It loads, it saves, it looks finished. The failure is invisible right up until someone finds it.
That’s why AI coding tools produce them so reliably. These tools optimise for code that runs, and every flaw below leaves code that runs perfectly.
This isn’t an argument against vibe coding. It’s the specific list of what to check before anything you built this way meets real users.
1. Authorization vs Authentication
The single most common flaw, by a wide margin.
Authentication is “who are you.” AI tools get this right almost every time — login works, sessions persist, protected routes redirect.
Authorization is “are you allowed to touch this specific thing.” This gets skipped constantly.
The result looks like this. You build a dashboard where users see their own invoices. The AI writes an endpoint at /api/invoices/[id]. It checks you’re logged in. It does not check the invoice belongs to you.
/api/invoices/1041 ← your invoice
/api/invoices/1042 ← someone else's invoice, also returned
Everything works. Every test passes. Every user can read every other user’s data.
How to check: Make two accounts. Log in as the first, note a record ID. Log in as the second and request the first account’s ID directly. If you can see it, you have this bug.
What to ask for:
Every query that reads or writes user-owned data must filter by the authenticated user’s ID at the database level, not just in the UI. Show me each place this is enforced.
2. Exposed Secrets
AI tools place API keys wherever the code is simplest, which is often the browser.
Three specific traps:
NEXT_PUBLIC_ variables are public. The prefix means “ship this to the browser.” An AI that hits an environment variable error will sometimes fix it by adding that prefix. Now your key is in the JavaScript bundle, readable by anyone who opens devtools.
Keys committed to Git. If it was ever in a commit, it’s in the history. Deleting it from the current file does nothing. Rotate the key.
Server keys used client-side. Supabase’s service_role key bypasses all row-level security. It belongs on the server only. It ends up in frontend code more often than you’d hope.
How to check:
git log -p | grep -iE "sk-|api[_-]?key|secret|password" | head -40
Then open your deployed site, view source, and search the bundle for key, sk-, and secret.
3. Row-Level Security Left Off
If your app uses Supabase, Firebase, or a similar hosted database, this one matters enormously.
These platforms expose the database directly to the browser. What stops a user reading the whole table is row-level security policies. AI tools generate working apps without enabling RLS all the time — because enabling it while the app is half-built causes errors, and the path of least resistance is to leave it off.
An app with RLS disabled and a public anon key means anyone can query your entire database from their browser console. Not your API. Your database.
How to check: In Supabase, open Table Editor and look for the RLS badge on every table. Any table without it is fully readable.
What to ask for:
Enable row-level security on every table and write policies so users can only select, insert, update, and delete their own rows. Show me the SQL.
4. No Input Validation
AI-generated forms validate in the browser and trust whatever arrives at the server. Browser validation is a convenience for honest users; it stops nobody who opens a terminal.
This produces oversized uploads, malformed data that corrupts your tables, and injection where the input reaches a query or a template.
What to ask for:
Validate every API input server-side with Zod. Reject anything that doesn’t match the schema before it reaches the database. Include maximum lengths on all string fields.
5. Open Endpoints and No Rate Limiting
If your app calls an AI API, sends email, or does anything that costs money per request, an unprotected endpoint is a billing incident waiting to happen.
The pattern: you build a tool that calls a language model. The endpoint is public because it needs to work before login. Someone finds it and loops it. You discover this via your invoice.
What to ask for:
Add rate limiting to every public endpoint — per IP and per user. Any endpoint that calls a paid API must require authentication and enforce a per-user quota.
6. Errors That Say Too Much
Default error handling returns stack traces, file paths, database schema, and sometimes connection strings. Useful while building; a map of your system in production.
What to ask for:
In production, return generic error messages to the client and log details server-side only. No stack traces in any API response.
7. Dependencies Nobody Chose
Agentic tools install packages to solve problems. Some are unmaintained, some are near-typosquats of the package they meant.
npm audit
Run it. Then read your package.json and confirm you can say what each entry is for. If you can’t, you didn’t choose it.
The Pre-Launch Checklist
Before anything you vibe coded touches real users:
- Two test accounts — confirm neither can access the other’s records by ID
-
git log -p | grep -i "key\|secret"returns nothing sensitive - No secrets in the client bundle (view source and search)
- Row-level security enabled on every table, with policies
- Every API input validated server-side
- Rate limiting on all public endpoints
- Any paid-API endpoint requires auth and has a per-user cap
- Generic error messages in production
-
npm auditclean, and you recognise every dependency - Logged out, tried to call your API directly — got rejected
Using AI to Audit AI
This works better than it sounds, because reviewing is an easier task than building and you can point it at one thing at a time.
Review this file as a security auditor. For every database query, tell me whether it filters by the authenticated user. List anything a logged-in user could access that isn’t theirs. Don’t fix anything yet — just report.
Asking it to report rather than fix keeps it from quietly rewriting things while you’re trying to understand them. Do this file by file for anything handling data, auth, or payments.
It won’t catch everything. It will catch the obvious tier, which is where most of these bugs live.
The Rule Worth Keeping
Vibe code freely up to the point where a stranger can reach it. At that boundary, read the code.
That’s not a limit on how much you can build this way — it’s most of an app. It’s a limit on which parts you ship without looking, and it’s the difference between a tool that works and a tool that quietly leaks.
Related reading: