REVIEWING AI-GENERATED CODE: THE SECURITY GUIDE
What this guide covers
This page is about reviewing AI-generated code at the IDE level — the suggestion you tab-complete, the function Cursor or Copilot writes into your buffer, the handler you accept from chat. If you’re reviewing large multi-file diffs produced by autonomous agents (Claude Code sessions, Devin runs, Composer agent mode), that’s a different problem with different tactics — diff triage by security tier, trust-boundary tracing, checking whether the agent weakened tests. For that, see the agent code review guide. The two are complementary: this page is the discipline for code you accept as you write; that page is the discipline for changesets you receive after the fact.
Why AI code review needs its own checklist
Reviewing a colleague’s code, you lean on assumptions that are usually safe: they know the codebase’s auth middleware exists, they wouldn’t invent a library function, their comments describe what the code does. None of those assumptions hold for a model. Specifically, human-code review takes for granted:
That the code’s claims are true. AI code frequently carries comments describing security behavior the code doesn’t implement — // sanitize input above unsanitized input. Review the code, never the comments.
That referenced functions and packages exist. Models generate plausible-looking calls to helper functions that exist nowhere in the codebase, and import packages that exist nowhere on the registry — the package hallucination problem. A human wouldn’t type an import they’d never installed; a model does it fluently.
That project conventions were followed. A colleague uses the shared requireAuth middleware because they know it’s there. A model that didn’t see it in context reimplements auth inline — often subtly worse (unverified jwt.decode(), missing expiry check) — and the diff still looks complete.
That omissions were decisions. When a human skips rate limiting on a login route, they usually made a call. When a model skips it, nothing was decided; the pattern simply wasn’t in the completion. Every absent control in AI code is unreviewed by default.
So the core mental shift: review AI code as if written by a fast, well-read contractor on day one — fluent in the language, zero knowledge of your codebase, no accountability for what breaks.
Checklists by change type
Generic 12-point checklists get skimmed. Scoped ones get used. Match the checklist to what the AI just generated:
New route or endpoint
- Auth middleware applied — and if the route takes an
:id, an ownership check, not just a login check (BOLA in AI CRUD) - Input validated against a schema (body, query, params) before any use
- Database access parameterized — no template-literal SQL, no string-built queries
- Error path returns a generic message; raw errors logged server-side only
- Response returns only the fields the client needs — no
SELECT *serialized straight out, no mass assignment on the write path
New form or client-side input
- Output encoding wherever the value is rendered — especially if it passes through markdown or HTML rendering (LLM-rendered HTML/markdown)
- Server-side validation exists independently of any client-side checks the AI added
- CSRF protection on state-changing submissions if you use cookie sessions
- No secrets or API keys referenced in client code — Vite/Next inline them into the public bundle
New dependency
- Package exists on the registry under exactly that name; check publish date and weekly downloads before installing
npm audit/pip-auditclean for the added version- The dependency is actually needed — models routinely import a package for something the standard library does
- Lockfile diff reviewed: one intended package, not a surprise dependency tree
New file upload handler
- File type validated by content, not just extension or client-supplied MIME type
- Size limit enforced before the body is buffered
- Storage path constructed without user input, or sanitized against
../traversal - Uploaded files served from a non-executing location (object storage or a static host, never the app’s route tree)
The prompt-based re-review technique
The model that wrote the code can also audit it — as long as you make it a separate pass with a fresh, adversarial framing rather than asking “is this secure?” in the same thread (which reliably produces “yes”). After accepting a nontrivial completion, run a prompt like:
“Act as a security reviewer who did not write this code. List every place in this diff where: input is used without validation, a route lacks an authorization or ownership check, an error or log statement could leak internals, a query is built from strings, or a secret appears in code. For each, cite the exact line. If a category has no findings, say so explicitly.”
Two details make this work. First, the explicit category list — open-ended “find vulnerabilities” prompts miss omissions, because absence has no line to point at; naming the categories forces the model to check for what isn’t there. Second, requiring line citations suppresses vague filler findings. Treat the output as a lead sheet, not a verdict: confirm each cited line yourself, and remember the model still can’t see the middleware and conventions outside its context window.
This costs about a minute per accepted change and consistently catches the comment-versus-implementation mismatches that skim review misses. It does not replace the checklist — it’s the fast pass before it.
Tooling gates: making the review survive a deadline
Manual discipline degrades under time pressure; the checks that matter most should not depend on it. Wire the mechanical parts of the checklist into gates that run without you:
- Pre-commit: secrets scanning (gitleaks or trufflehog) — the single highest-value gate for AI-assisted work, since hardcoded keys are the most common AI-introduced critical
- CI, per push: SAST with rules tuned to AI failure patterns (see SAST tools for AI code), plus dependency audit on any lockfile change
- CI, per deploy: a black-box pass against the running app for what static tools can’t see — permissive CORS, exposed source maps, missing headers, reachable debug routes. This is the layer VibeEval automates; between SAST and pentest explains where it sits
- Branch protection: AI-heavy changes merge via PR, never direct to main — which also hands you the diff surface the agent review guide works on
The division of labor that results: gates catch the pattern-shaped problems (secrets, injection, known-vulnerable dependencies), your review catches the judgment-shaped ones (is this the right trust boundary, should this route exist at all), and the prompt-based pass bridges the two.
Related resources
- Agent Code Review Guide — the companion guide for autonomous-agent diffs
- AI Code Quality vs Security — why clean-looking AI code still ships vulnerable
- Secure AI Coding Practices — preventing the findings upstream, in prompts and rules files
- Vibe Coding Vulnerabilities — the taxonomy behind the checklists
- Cursor Security Risks — the 12 patterns to expect from Cursor specifically
- Copilot Security Risks — the same analysis for GitHub Copilot
SCAN YOUR DEPLOYED APP
Paste your live URL. We probe exposed keys, missing auth, open databases, and broken access control — results in under 60 seconds. 14-day trial, no card.
14-day free trial · No credit card · Cancel anytime