TL;DR: The six vulnerabilities AI-generated code most commonly introduces are: (1) hardcoded secrets, (2) SQL injection via string concatenation, (3) XSS via unescaped output, (4) IDOR — accessing other users' data, (5) missing rate limiting on auth endpoints, (6) broken authentication (no token expiry, weak secrets). Each is fixable with a specific pattern.

Why AI Coders Need to Know This

AI coding tools are trained on vast amounts of code — including vulnerable code. They reproduce patterns statistically, not securely. A model that has seen thousands of examples of SQL string concatenation will generate SQL string concatenation. A model that has seen tutorials storing API keys in source files will generate API keys in source files.

This isn't AI being malicious — it's AI being statistically average. Security-correct code is a minority of all code ever written. The result is that AI-generated apps, deployed without security review, often contain vulnerabilities that a junior developer would catch in code review. This guide is that code review.

Vulnerability 1: Hardcoded Secrets

🔴 What AI Generates

// Seen in AI-generated code constantly
const stripe = new Stripe('sk_live_abc123xyz789...');  // real key in source
jwt.sign(payload, 'mysecret');                          // weak, hardcoded
const db = new Pool({ password: 'mypassword' });       // database password in code

✅ Fix: Environment variables. Every secret belongs in .env (locally) and your hosting platform's secret management (Vercel, Railway, etc.). Add .env to .gitignore immediately. If you've ever committed a real secret, rotate it — consider it compromised.

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
jwt.sign(payload, process.env.JWT_SECRET!);
// JWT_SECRET should be a 32+ character random string

Vulnerability 2: SQL Injection

🔴 What AI Generates

// String interpolation in SQL — never do this
const query = `SELECT * FROM users WHERE email = '${userEmail}'`;
// If userEmail = "' OR '1'='1" → returns ALL users

✅ Fix: Parameterized queries or an ORM. User input must always be passed as a parameter, never interpolated into the SQL string.

// Parameterized (node-postgres)
const result = await db.query('SELECT * FROM users WHERE email = $1', [userEmail]);

// ORM (Prisma) — always safe
const user = await prisma.user.findUnique({ where: { email: userEmail } });

Vulnerability 3: XSS (Cross-Site Scripting)

🔴 What AI Generates

// React — dangerouslySetInnerHTML without sanitization
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// If userContent = "<script>stealCookies()</script>" → executes in browser

// Express — unescaped template output
res.send(`<h1>Hello ${req.query.name}</h1>`);
// If name = "<script>alert(1)</script>" → XSS

✅ Fix: React's JSX escapes content by default — use it. When you must render HTML, sanitize with DOMPurify. In Express, use a templating engine that escapes by default (EJS, Handlebars) or escape manually.

// React — let JSX escape it (safe by default)
<div>{userContent}</div>

// If you MUST render HTML, sanitize first
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />

Vulnerability 4: IDOR — Insecure Direct Object Reference

🔴 What AI Generates

// Fetches any invoice by ID — no ownership check
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
  const invoice = await db.invoice.findUnique({ where: { id: req.params.id } });
  res.json(invoice);
  // Any logged-in user can access any invoice by guessing IDs
});

✅ Fix: Always filter by both the resource ID and the authenticated user's ID. If the query returns null with both conditions, the user either doesn't own the resource or it doesn't exist — treat both as 404.

app.get('/api/invoices/:id', requireAuth, async (req, res) => {
  const invoice = await db.invoice.findFirst({
    where: {
      id: req.params.id,
      userId: req.user.id  // ← must belong to this user
    }
  });
  if (!invoice) return res.status(404).json({ error: 'Not found' });
  res.json(invoice);
});

Vulnerability 5: Missing Rate Limiting

🔴 What AI Generates

// Login endpoint with no rate limiting
app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  // An attacker can try 10,000 passwords per second
  const user = await validateCredentials(email, password);
  // ...
});

✅ Fix: Rate limit auth endpoints. Also consider account lockout after N failures.

import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15 minutes
  max: 10,                     // 10 attempts per window per IP
  message: 'Too many login attempts, try again in 15 minutes',
  standardHeaders: true,
});

app.post('/api/auth/login', loginLimiter, async (req, res) => { ... });

Vulnerability 6: Broken Authentication

🔴 What AI Generates

// Missing expiry on JWT
jwt.sign(payload, secret);  // no expiresIn — token is valid forever

// Token in localStorage (XSS-vulnerable)
localStorage.setItem('token', token);

// Weak secret
jwt.sign(payload, 'secret');  // trivially brutable

✅ Fix: Always set expiry. Use a strong random secret (32+ chars). Store tokens in httpOnly cookies, not localStorage.

// Strong JWT config
jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '15m' });
// Use refresh tokens for longer sessions

// Store in httpOnly cookie, not localStorage
res.cookie('auth-token', token, {
  httpOnly: true,   // JS can't read it
  secure: true,     // HTTPS only
  sameSite: 'lax',  // CSRF protection
  maxAge: 15 * 60 * 1000  // 15 minutes
});

The AI Security Review Prompt

After generating any backend code, run this prompt in Claude Code or Cursor:

Review this code for security vulnerabilities. Check specifically for:
1. Hardcoded secrets or API keys
2. SQL queries built with string concatenation (SQL injection risk)
3. User input rendered without escaping (XSS risk)
4. API endpoints that don't verify resource ownership (IDOR risk)
5. Missing rate limiting on auth endpoints
6. JWT configuration issues (no expiry, weak secret, localStorage storage)
7. Missing authentication on protected routes
8. Sensitive data in error messages returned to clients

For each issue found, show the vulnerable code and the fix.

This prompt takes 30 seconds and catches the majority of AI-introduced security issues before they reach production.

What to Learn Next

Frequently Asked Questions

XSS is a vulnerability where an attacker injects malicious JavaScript into a web page viewed by other users. The script runs in victims' browsers and can steal session cookies, redirect users, or log keystrokes. It occurs when user input is displayed on a page without sanitization. React's JSX escapes output by default, preventing most XSS — but dangerouslySetInnerHTML bypasses this protection.

SQL injection occurs when user-supplied input is inserted directly into a SQL query string, allowing an attacker to modify the query's logic. An attacker can bypass login, read all database records, or delete data. Prevention: always use parameterized queries or an ORM (Prisma, Drizzle) that handles escaping automatically. Never build SQL strings from user input.

IDOR (Insecure Direct Object Reference) is when an API exposes internal IDs that users can manipulate to access other users' data. For example, /api/invoices/123 — changing 123 to 124 returns another user's invoice. Fix: always verify that the authenticated user owns or has permission to access the requested resource, not just that the resource exists.

Paste your code into Claude and ask: 'Review this code for security vulnerabilities, focusing on: SQL injection, XSS, IDOR, hardcoded secrets, missing authentication on routes, and rate limiting.' Run npm audit to check for vulnerable dependencies. Use tools like Snyk or GitHub's Dependabot for automated scanning. For authentication code specifically, use a battle-tested library instead of rolling your own.

In priority order: (1) Remove all hardcoded secrets — move to environment variables. (2) Verify all API routes check authentication. (3) Add rate limiting to login and registration endpoints. (4) Ensure database queries use parameterized queries, not string concatenation. (5) Validate and sanitize all user input on the server side. (6) Set secure cookie flags (httpOnly, Secure, SameSite).