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-audit clean 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.

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

START FREE SCAN