AI tools generate code faster than any team can read it. That is the point of them. But generation without a review step is how a critical defect reaches production wearing clean formatting and sensible variable names.
This is the checklist to run before AI-generated code ships. It is ordered deliberately: the first two checks catch the defects that cause incidents, so if you only have five minutes, spend them at the top.
Why does AI-generated code need a different review process?
Traditional review is a senior engineer reading a diff and applying pattern recognition. That works well on human code, because human defects correlate with human signals: rushed naming, a tangled function, a comment that says "temporary".
AI-generated code removes every one of those signals. Naming is consistent, structure is clean, comments are complete. The surrounding hundred lines are genuinely good, which creates a strong impression of quality precisely where the one bad line is hiding. Human reviewers relax exactly where they should not.
1. Authorisation boundaries (highest yield, start here)
For every endpoint that returns or mutates user data, answer one question: if I change an ID in this request to a different user's ID, what comes back?
The safe pattern is to make ownership part of the query rather than a check before it. A separate check can be skipped, reordered, or refactored away; a constraint inside the query cannot be, because removing it changes what the query returns.
// Fragile: the guard is separate from the fetch
const doc = await Document.findById(id)
if (doc.userId !== session.userId) return res.status(403).end()
// Durable: a non-owner simply gets no row
const doc = await Document.findOne({ _id: id, userId: session.userId })
if (!doc) return res.status(404).end()Note the 404 rather than 403 in the second version. Returning 403 confirms the record exists, which hands an attacker a working existence oracle for free.
2. External calls: error, timeout, fallback
Every fetch, axios call, or SDK invocation needs three things. Models produce the first inconsistently and the second and third almost never.
- A try/catch, or an explicit response-status check. A non-2xx response is not an exception in most HTTP clients, so
await fetch()succeeding tells you nothing about whether it worked. - A timeout. Without one, a hanging upstream service holds your request open until your own platform kills it, and one slow dependency becomes your outage.
- A defined failure state in the UI. If the call fails, the interface must say so. A spinner that never resolves is indistinguishable from your product being broken.
3. Query construction
Any query incorporating user input must be parameterised or built through an ORM. String interpolation into SQL is an injection vulnerability regardless of how harmless the input looks in testing, and template literals make it read like clean modern JavaScript, which is exactly why it survives review.
// Vulnerable: reads fine, is not fine
const q = `SELECT * FROM users WHERE email = '${email}'`
// Parameterised
const q = 'SELECT * FROM users WHERE email = $1'
await db.query(q, [email])This pattern and the rest of the recurring security shapes are covered in more depth in security risks in Cursor and Copilot code.
4. Business logic against written rules
This check is different from the others: it cannot be performed by reading the code alone. Nothing in a pricing function tells you whether 18% is the right rate, whether the discount applies before or after tax, or whether that ordering differs by country.
So write the rule down first, in one or two sentences, and then check the implementation against the sentence. "Discount applies to the pre-tax subtotal. GST is 18%, or 5% on essential goods, calculated after the discount." Now the review is a comparison rather than a guess, and it is the only way to catch a function that computes the wrong number without any defect in the code.
5. Response shape and data exposure
Look at what each endpoint actually returns, not what it is supposed to return. Serialising a whole database row is the default AI pattern and it commonly leaks password hashes, internal flags, soft-delete markers, or other users' identifiers embedded in nested relations. Select fields explicitly; never return a raw model.
How to make this actually happen under deadline
Applied by hand, this checklist takes fifteen to forty minutes per file. That is sustainable for a week and then it quietly stops happening, usually in the sprint before launch, the one where the most code is being generated and the least is being read.
The realistic answer is to automate the five checks so they run in parallel in seconds and return findings graded by severity, and to reserve human judgement for the decisions that genuinely need it. Nexdge runs exactly these five as separate specialists (security, reliability, business logic, performance, and quality) and returns a verified rewrite alongside the findings.