
Securing AI-Generated Code: A Practical Audit Checklist
Why AI-generated code needs a real audit
AI-generated code usually looks plausible on the first read. That is the trap.
I keep seeing the same pattern in generated JavaScript: the happy path is tidy, the helper names are polished, and the edge cases are missing. The code assumes trusted input, a trusted user, and a trusted database. That is where the bug starts.
The useful question is not “does it compile?” It is “what did the model assume without saying so?” If you audit for that, you catch most of the damage early.
What to check before the code reaches review
Before anyone gets attached to the implementation, look for two risk buckets: new dependencies and new defaults.
Dependency changes and hidden supply-chain risk
Generated code loves convenience packages. Sometimes that is harmless. Sometimes it quietly adds a parser, date library, request helper, or queue client when a built-in would do.
Check:
- whether the dependency is actually needed
- whether the package is well maintained
- whether the change introduces postinstall scripts or native build steps
- whether the package changes runtime behavior in ways the diff does not explain
If the feature only needs URL, fetch, or crypto, I usually push back on pulling in another package. Fewer dependencies mean less to audit and less to patch later.
Dangerous defaults in generated helpers
Generated helpers often bake in bad assumptions:
allowGuest: trueincludePrivate: falsein the wrong placeignoreErrors: truelimit = 1000because “performance”res.status(200)even when validation fails
These are not style issues. They change trust boundaries. A helper that “makes it work” can also make the attack path easier.
A practical audit checklist for JavaScript code
This is the checklist I use when reviewing AI-generated JavaScript.
Input handling and trust boundaries
Ask where every external value comes from:
- request body
- query string
- headers
- cookies
- local storage
- database fields that were originally user input
- third-party API responses
Then verify that the code:
- validates shape and type
- rejects unexpected fields
- normalizes values before use
- treats client input as untrusted even after basic validation
A common bug is using input directly in a selector, filename, route, or query without narrowing it first. Another is trusting a client-side flag like isAdmin or canEdit.
Authentication and authorization checks
Authentication proves identity. Authorization proves access. Generated code often checks the first and forgets the second.
Look for:
- account ownership checks on read and write paths
- org or workspace membership checks
- role checks on admin-only actions
- object-level access checks before updates and deletes
A route like this should make you pause:
app.delete("/api/projects/:id", async (req, res) => {
await db.project.delete({ where: { id: req.params.id } });
res.sendStatus(204);
});
It works. It also lets any authenticated user delete any project if the ID is known. The fix belongs on the backend, not in the UI.
Secrets, logs, and environment leaks
Generated code sometimes logs too much because it is trying to be helpful.
Check for:
console.log(req.body)- logging tokens, session cookies, or full headers
- returning stack traces to clients
- exposing
.envvalues through debug endpoints - accidental inclusion of secrets in test fixtures
If a secret appears in a log line, assume it will live longer than intended. Logs are replicated, shipped, indexed, and retained. That is a security boundary, not a scratchpad.
Data access and unsafe queries
Generated ORM code is often better than handwritten string concatenation, but don't stop there.
Review:
- raw SQL strings built from request values
- dynamic sort fields and filter keys
whereclauses that skip tenant scoping- bulk operations that ignore ownership
- cached data reused across users
A safe-looking query can still be wrong if it is missing the tenant or account filter. That is a data leak, not just a logic bug.
How I test the output in a safe workflow
I do not start by reading the whole diff. I start by trying to break the feature in a small environment.
Reproduce the feature with a minimal case
Strip the code down to one request, one handler, one database row. Then ask:
- what if the user is unauthenticated?
- what if the ID belongs to someone else?
- what if a field is missing?
- what if the input is longer than expected?
Small repros are easier to reason about than full app flows, and they make missing checks obvious.
Add negative tests for abuse paths
Generated code often has one happy-path test and nothing else. That is not enough.
Add tests for:
- forbidden access
- invalid types
- empty payloads
- duplicate requests
- unexpected extra fields
- cross-tenant object access
Negative tests are the fastest way to catch “looks fine” security regressions before they ship.
Review the diff like an attacker would
I read generated changes with three questions:
- What did the code trust that it should not?
- What changed in access control?
- What error path might leak data or bypass checks?
That mindset turns review from syntax checking into boundary checking.
What to automate and what to keep manual
Automation should catch the repeatable stuff:
| Layer | Automate | Keep manual |
|---|---|---|
| Linting | unsafe patterns, unused vars, obvious eval usage | business logic assumptions |
| Tests | auth failures, invalid input, edge cases | whether the access model is correct |
| SAST | known risky APIs, injection sinks | whether a query is scoped correctly |
| CI checks | dependency diffs, lockfile changes | whether a helper is appropriate |
I would automate detection of dangerous APIs and missing tests. I would not automate judgment about whether a feature should exist with that permission model in the first place.
Fix patterns that belong in the backend
Some fixes do not belong in the generated file at all:
- authorization on object access
- tenant scoping for every query
- server-side validation of privileged fields
- secret handling and redaction
- audit logging for sensitive actions
If the frontend can influence it, the backend must verify it. If the generated code tries to enforce security with only client-side checks, treat that as a draft, not a defense.
Conclusion
AI-generated code is useful when it shortens boilerplate. It is risky when it shortens your review.
My rule is simple: accept the speed, not the trust. Verify input handling, access control, data scope, and secret exposure before the code reaches review. If you do that consistently, the model can save time without deciding your security posture for you.


