Vibe Coding Security Risks: What AI-Generated Code Gets Wrong
AI writes code that runs, looks right, and passes review — while quietly reproducing the most common vulnerabilities. Here's what AI-generated code gets wrong, and why.
Why AI code is plausibly insecure
AI coding assistants are pattern machines trained on public code — including its mistakes. They produce output that is fluent, idiomatic, and plausible. The danger isn't that the code looks wrong; it's that insecure code looks exactly as right as secure code. Here's the recurring set of things they get wrong, and the reason behind each.
1. Missing authorization (the big one)
AI reliably builds authentication — login, sessions, "is this a user?" It just as reliably forgets authorization — "is this user allowed to touch this record?"
// What AI writes — works in the demo, leaks in production
const order = await getOrder(req.params.id);
return Response.json(order);
Any logged-in user can read any order by changing the ID. This is IDOR / Broken Access Control, OWASP's #1 risk. The fix is one line the model leaves out:
if (order.userId !== session.user.id) return forbidden();
Why it happens: the demo only ever has one user, so the missing check never surfaces during the vibe.
2. Hardcoded secrets
Ask for a database or Stripe connection and you'll often get the key inline. It works immediately, so it survives.
const stripe = new Stripe("sk_live_51Hxxxx..."); // shipped to your repo, maybe your bundle
Why it happens: env-var plumbing is extra friction the model skips unless told. Once it's in git history, the key is compromised — rotate it.
3. SQL injection via string templates
Template literals read cleanly, so the model prefers them:
const rows = await db.query(`SELECT * FROM users WHERE email='${email}'`);
A user with email ' OR '1'='1 dumps your table. Parameterize:
const rows = await db.query("SELECT * FROM users WHERE email=$1", [email]);
Why it happens: insecure string-built SQL is everywhere in the training data.
4. No input validation
AI codes the happy path. It assumes the request body is the shape it expects, the number is positive, the string isn't 10MB. Real users (and attackers) don't cooperate. Add schemas at every boundary with zod or similar.
5. Disabled database security to "make it work"
When the AI hits a permissions error with Supabase or Firebase, a frequent "fix" is to turn the protection off — disable RLS, set rules to allow read, write: if true. The error goes away; the database goes public.
Why it happens: the model resolves the immediate symptom (an error) rather than the cause (a missing policy).
6. The nuclear CORS fix
Asked to fix a CORS error, the model reaches for Access-Control-Allow-Origin: * — sometimes with credentials — exposing your authenticated API to every origin.
7. Dangerous rendering
dangerouslySetInnerHTML and innerHTML on user-supplied content reintroduce XSS that React otherwise prevents.
8. Leaky error handling
Generated catch blocks love to return error.message (and stack) to the client, handing attackers a map of your internals.
The pattern behind the pattern
Every one of these comes from the same root cause: the AI optimizes for "it runs in the demo," and the demo has no adversary. Security failures are invisible until someone hostile shows up — and by then it's production.
How to vibe-code more safely
- Give the model rules. In
.cursorrulesor your system prompt: always parameterize SQL, validate input, check resource ownership, never hardcode secrets, never disable RLS. - Review security-relevant diffs yourself. Never accept auth, data-access, or query code you don't understand.
- Make the model audit itself. "Check this diff against the OWASP Top 10" catches a lot.
- Test as a second user and as a logged-out user. That surfaces the missing authorization instantly.
- Scan before you ship.
Scan it with Troja
Troja was built for the vibe-coding era: it finds the missing ownership check, the hardcoded key, the string-built SQL, the disabled RLS — and hands you a fix prompt to paste straight back into your AI agent. Let the AI write it; let Troja check it.
Run the scan this post is about.
Free, no signup. See what's hiding inside your walls in ~30 seconds.