HomeFeaturesPricingDocumentationContactDOWNLOAD

AI-generated code audit: 7 mistakes before shipping

Audit AI-generated code for exposed secrets, swallowed errors, authorization gaps, unsafe SQL, reckless retries, and stale React effects.

AI coding tools are good at producing a plausible implementation quickly. Plausible is not the same as safe under failure, hostile input, repeated clicks, or a changing application state.

The recurring problems are rarely exotic. They are ordinary engineering mistakes that compile cleanly and look reasonable in a review. Use this checklist after a substantial Cursor, Claude Code, Copilot, or Codex session and before the code reaches production.

1. Secrets crossed into client code

The most expensive mistake is often a provider key imported into browser code. Frameworks make this easy to do accidentally through public environment-variable prefixes, shared configuration modules, or an SDK initialized in a client component.

Search the production bundle, not only the source:

rg "sk_|api[_-]?key|secret|token" dist .next/static build

Treat every browser bundle as public. Move privileged provider calls behind a server route, keep secrets in the deployment platform’s secret store, and restrict the key at the provider when possible.

2. Catch blocks erased the failure

AI-generated functions frequently catch every error, log it, and return null:

try {
  return await fetchUser(id);
} catch (error) {
  console.error(error);
  return null;
}

Now the caller cannot distinguish a missing user from a database outage. The system appears to continue while the real failure disappears into a console nobody monitors.

Catch an error only when that layer can recover, translate it into a meaningful domain result, or attach context before rethrowing. Persist operational failures in structured logs or an error tracker.

3. Authentication was mistaken for authorization

Checking that a request has a signed-in user does not prove that user may access the requested record.

A generated route often looks like this:

const user = await requireUser(request);
return db.project.findUnique({ where: { id: params.id } });

The query needs an ownership, membership, or role constraint. Otherwise any authenticated user who guesses another identifier can read the record.

Review every create, read, update, delete, export, and file-download operation. Ask both questions: who is the caller, and why may this caller act on this specific resource?

4. Sensitive values used ordinary string comparison

HMAC signatures, webhook secrets, and other authentication values should use the platform’s timing-safe comparison primitive after validating equal lengths. Generated code often reaches for === because it is familiar.

This is especially important in verification endpoints that are reachable repeatedly over the network. Use the provider’s official verification library where one exists, and test a known valid signature plus altered payloads.

5. SQL was assembled with string interpolation

The model knows parameterized SQL exists and can still generate this under time pressure:

const query = `SELECT * FROM users WHERE email = '${email}'`;

Do not attempt to escape values yourself. Use query parameters or a query builder that keeps values separate from SQL syntax.

Search for SQL keywords near template literals and string concatenation. Then test quotation marks, Unicode, empty values, and authorization boundaries, not only the happy-path email used during development.

6. Retry logic ignored status and idempotency

Generated retry helpers often retry every failure with a fixed delay. That can amplify an outage, repeat a payment, or waste time retrying a permanent 400 response.

A deliberate retry policy defines:

  • Which network failures and status codes are retryable
  • A maximum attempt count and total time budget
  • Exponential backoff with jitter
  • Request cancellation
  • Idempotency for any operation with side effects

Test the helper against timeouts, 429, transient 5xx, permanent 4xx, and a caller that cancels midway through the backoff.

7. React effects captured stale state

This pattern can pass a demo and fail as soon as a prop changes:

useEffect(() => {
  fetchData(id);
}, []);

Adding an ESLint-disable comment hides the warning rather than fixing the dependency model. Depending on id may be correct, but the effect also needs cancellation or request identity so a slow earlier response cannot overwrite a newer one.

Look for disabled hook rules, async work without cleanup, duplicated event subscriptions, and effects that derive state React could calculate during render.

Run the audit in layers

Start with deterministic searches for secrets, string-built SQL, ignored lint rules, unrestricted filesystem access, unsafe command execution, and broad catch blocks. Then follow the data flow through authentication, authorization, network failure, and repeated user actions.

SiteCMD’s Code Scan automates many of those repeatable checks locally. The AI-assisted developer workflow explains how findings can move back into the editor as specific fix instructions without uploading the repository.

For a change that is already written, use the separate AI-generated code verification loop to review the diff, prove the regression test fails without the patch, and recheck the behavior at the layer where it originally failed.

Before shipping AI-assisted code

  • Production bundles contain no credentials or privileged provider SDKs
  • Errors remain observable and preserve useful context
  • Every resource operation checks authorization, not just authentication
  • Signatures and sensitive values use approved verification primitives
  • Database queries keep syntax separate from values
  • Retries are bounded, selective, cancellable, and idempotent
  • Effects include correct dependencies and cleanup
  • Tests cover hostile input, failure paths, and repeated actions

AI makes implementation faster. It does not remove the need for a threat model, a failure model, or verification. Treat generated code as a draft that earned a careful review, then use deterministic checks to make that review repeatable.