#code-review#security#ai-coding#best-practices

AI Code Review Checklist: What to Check Before Merging AI-Generated Code

A practical checklist for reviewing AI-generated code before it hits production. Covers security, correctness, and maintainability.

AI coding assistants can write a lot of code quickly. But speed means nothing if that code introduces security vulnerabilities, logic errors, or maintenance nightmares.

Here’s a practical checklist for reviewing AI-generated code before it reaches production.

Security Checks

These are non-negotiable. AI models sometimes include patterns from their training data that are insecure or outdated.

1. Hardcoded Secrets

Look for API keys, passwords, tokens, or credentials embedded in the code:

// 🚨 BAD: AI sometimes generates placeholder secrets
const apiKey = "sk-1234567890abcdef";
const password = "admin123";

// βœ… GOOD: Use environment variables
const apiKey = process.env.API_KEY;

Search the diff for patterns like:

  • api_key, apiKey, API_KEY
  • secret, password, token
  • sk-, pk_, Bearer
  • Base64 strings that look like credentials

2. SQL Injection

AI often generates string concatenation for SQL, which is vulnerable:

// 🚨 BAD: SQL injection vulnerability
const query = `SELECT * FROM users WHERE id = ${userId}`;

// βœ… GOOD: Parameterized queries
const query = `SELECT * FROM users WHERE id = $1`;
await db.query(query, [userId]);

3. XSS Vulnerabilities

Check that user input is properly escaped before rendering:

// 🚨 BAD: XSS vulnerability
element.innerHTML = userInput;

// βœ… GOOD: Use textContent or sanitize
element.textContent = userInput;

4. Insecure Defaults

AI sometimes uses permissive defaults:

// 🚨 BAD: CORS wide open
cors({ origin: "*" })

// βœ… GOOD: Specific origins
cors({ origin: ["https://myapp.com"] })
// 🚨 BAD: Weak crypto
crypto.createHash("md5")

// βœ… GOOD: Strong crypto
crypto.createHash("sha256")

Logic & Correctness

AI can write code that looks right but has subtle bugs.

5. Edge Cases

Does the code handle:

  • Empty arrays/objects?
  • Null/undefined values?
  • Negative numbers where only positive make sense?
  • Very large inputs?
// 🚨 Missing null check
function getFirstItem(items) {
  return items[0].name;  // Crashes if items is empty
}

// βœ… With null check
function getFirstItem(items) {
  return items?.[0]?.name ?? null;
}

6. Error Handling

AI often generates the happy path without proper error handling:

// 🚨 No error handling
const data = await fetch(url);
const json = await data.json();
return json.result;

// βœ… With error handling
try {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const data = await response.json();
  return data.result;
} catch (error) {
  console.error("Fetch failed:", error);
  throw error;
}

7. Off-by-One Errors

Classic bug that AI reproduces:

// 🚨 Off-by-one: skips last element
for (let i = 0; i < items.length - 1; i++)

// βœ… Correct
for (let i = 0; i < items.length; i++)

8. Async/Await Issues

AI sometimes forgets await or mishandles promises:

// 🚨 Missing await
function saveUser(user) {
  db.save(user);  // Returns immediately, save may fail silently
  return { success: true };
}

// βœ… With await
async function saveUser(user) {
  await db.save(user);
  return { success: true };
}

Maintainability

Code that works today but is unmaintainable is a liability.

9. Naming & Clarity

AI sometimes uses generic names:

// 🚨 Unclear naming
const data = await getData();
const result = processData(data);
const x = transform(result);

// βœ… Clear naming
const users = await fetchActiveUsers();
const validatedUsers = validateUserData(users);
const userDTOs = mapToResponseFormat(validatedUsers);

10. Unnecessary Complexity

AI can over-engineer simple problems:

// 🚨 Over-engineered
class UserValidatorFactory {
  createValidator(type) {
    return new UserValidator(new ValidationStrategy(type));
  }
}

// βœ… Just a function
function validateUser(user) {
  return user.email && user.name;
}

11. Dead Code

AI sometimes generates code that’s never used:

// 🚨 Unused function
function helperThatNothingCalls() {
  // ...
}

// Delete it or ask: why did AI generate this?

12. Matches Project Conventions

Does the AI code match your existing patterns?

  • Import style (named vs default)
  • Error handling patterns
  • Logging approach
  • File/folder structure

Quick Checklist

Copy this for your PR reviews:

## AI Code Review Checklist

### Security
- [ ] No hardcoded secrets/API keys
- [ ] No SQL injection (parameterized queries used)
- [ ] No XSS (user input escaped)
- [ ] Secure defaults (CORS, crypto, etc.)

### Correctness
- [ ] Edge cases handled (null, empty, bounds)
- [ ] Proper error handling
- [ ] No off-by-one errors
- [ ] Async/await used correctly

### Maintainability
- [ ] Clear naming
- [ ] No unnecessary complexity
- [ ] No dead code
- [ ] Matches project conventions

Automate What You Can

Manual review catches a lot, but automated tools catch more consistently.

Static analysis tools like ESLint with security plugins can flag common issues. Security scanners like Snyk or npm audit catch known vulnerabilities.

mrq runs automatic security audits on every code change, flagging hardcoded secrets, SQL injection patterns, and other issues before you even start reviewing. It’s like having a security-focused first pass on every diff.

The Bottom Line

AI-generated code is like code from a very fast junior developer: often correct, sometimes subtly wrong, occasionally a security risk.

Review it the same way you’d review any code: trust but verify. Use this checklist, automate what you can, and always check security-sensitive areas carefully.


mrq automatically audits AI-generated code for security issues. Every snapshot is scanned for hardcoded secrets, injection vulnerabilities, and more.

Written by mrq team