Skip to content

How to Security-Audit AI-Generated Code

Audit AI-generated code the way you would audit code from a new contractor you cannot question: map the trust boundaries, then attack each one with a specific test, and record every finding so it can be reproduced. The highest-value targets are authorization, secret handling and anywhere untrusted input reaches a database, a shell or a rendered page. This is a practical method, not a compliance framework, and no audit proves the absence of vulnerabilities.

Written by Vibrr Engineering · Published · Claims last verified · Not independently reviewed

Vibrr is an independent product. It is not affiliated with, endorsed by or sponsored by Lovable, Cursor, Claude Code and Replit. Product names are trademarks of their respective owners and are used here only to describe compatibility and content.

Start from the right assumption

Treat generated code as plausible, not verified. It often looks conventional, which makes problems harder to spot rather than less likely. The OWASP Top 10 is the standard general reference for the classes of weakness worth checking; use it to prompt questions, not as a checklist to tick.

Step 1 — Map the trust boundaries

A trust boundary is anywhere data or control crosses from something you do not trust to something you do. List them before reading any code.

Typical trust boundaries in a generated web app
BoundaryWhat crosses itFirst question
Browser → APIEvery request parameter, header and bodyIs each one validated and authorized on the server?
API → databaseQueries built from inputCan input change the meaning of a query?
API → other servicesCredentials and user dataWhere do the credentials live and who can read them?
User content → rendered pageText a user typed, shown to othersIs it escaped on output?
Repository → deploymentEnvironment variables and build outputDoes anything secret end up in the client bundle?

Step 2 — Test authorization by attacking it

  • Build the role × resource × operation matrix from the specification and run every cell against the backend.
  • Repeat each request with another user's identifier substituted in.
  • Repeat each request with no credentials.
  • Check that list endpoints return only what the caller may see, not everything with the interface filtering it.

Step 3 — Hunt for secrets

Search the working tree, the git history and the built client bundle for key-shaped strings, and read the environment-variable documentation for anything a client should never hold. Generated code sometimes places a credential where the interface can read it because that was the shortest path to a working demo.

Step 4 — Follow untrusted input to its sinks

  • Find every place input reaches a query, a shell command, a file path, an HTML template or a redirect.
  • Confirm each uses a safe API (parameterized queries, argument arrays, output escaping) rather than string concatenation.
  • Try a hostile value at each one and see what happens.

Step 5 — Review what you did not write

List direct dependencies, question any you cannot explain, and run your package manager's audit. A dependency added to make one demo feature work is a permanent piece of your attack surface.

Step 6 — Make every finding reproducible

A finding is only useful if someone else can reproduce and fix it. Record it in a fixed shape.

Finding record (Vibrr template)
Title: <one line>
Severity: critical | high | medium | low
Where: <route, file or boundary>
How to reproduce: <exact steps or request>
Expected: <what should happen>
Observed: <what happened>
Fix: <the change, with a keep-list>
Verify: <the test that fails before and passes after>

Feed each finding back to your coding tool as one change with a keep-list, then re-run the test that reproduces it. Claude Code's documentation makes the related point that a reviewer in a fresh context, seeing only the diff and the criteria, evaluates the result more independently than the session that wrote it.

Step 7 — Prioritise what you found

A long list of findings is only useful if it is ordered. Rank by what an attacker gains and how easily, not by how alarming the description sounds.

A simple severity rubric (Vibrr guidance; adapt it to what the app protects)
SeverityTypical meaningExample
CriticalUnauthenticated or cross-user access to private data or actionsChanging another user's record by editing an identifier in a request
HighAccess beyond a role's intent, or a credential exposed to the clientA viewer role able to call an administrative endpoint
MediumWeakness that needs another flaw or unusual conditions to exploitMissing rate limit on a sign-in endpoint
LowHardening gap with little direct impactVerbose error messages

The examples are illustrative, not findings from any real application. Fix critical and high items before anything else, and record any item you consciously accept along with the reason.

What this method cannot show

For the wider launch checklist, see the production checklist for vibe-coded apps. For a worked platform example, see taking a Lovable app to production.

Frequently asked questions

Is AI-generated code less secure than human-written code?

Vibrr has no measurement that answers this, and this guide does not claim it. It treats generated code as unverified, which is the right stance for any code you did not write and cannot question.

What is the highest-value check?

Server-side authorization, tested by calling the backend directly as each role.

Can I use this instead of a penetration test?

No. For applications handling payments, health or other regulated data, commission a professional test.

Official and primary sources