AI-Generated Code Security: The Checklist to Run Before Merging

The Vulnerability That Ships Because the Code Looked Clean
Generated code has a specific talent for looking finished. It compiles, the happy path works, the style matches the file around it, and review attention drifts elsewhere.
Security flaws in that code are rarely dramatic. They are absences, such as a missing permission check, an unvalidated input, or a dependency nobody verified, and absences do not show up as red lines in a diff.
Human review evolved to catch human mistakes. Models make different mistakes with total confidence, so teams need a checklist tuned to what generation predictably gets wrong.
That checklist is this article. Eight checks, each tied to a documented failure pattern, sized to run in minutes at the pull request boundary.
Why Generated Code Fails Security Review in Predictable Ways

Models learn from public code, and public code is a museum of dated practice. Patterns that were acceptable in old tutorials, such as weak hashing or string-built queries, resurface in fresh suggestions decades later.
Generation also optimises for plausibility rather than safety. The most statistically likely completion of a database call is one that works, not one that defends against hostile input.
Volume changes the arithmetic as much as quality does. A team accepting dozens of generated changes weekly multiplies every small per-change risk, so a flaw rate that felt tolerable at human writing speed stops being tolerable at generation speed.
Context blindness compounds it. The model cannot see your threat model, your trust boundaries, or which of your endpoints faces the public internet, so it defaults to the least defended version that satisfies the prompt.
None of this argues against using assistants. It argues for treating generated code as competent-but-unbriefed contractor work, which is exactly what review checklists were invented for. A related but separate question is what the assistant does with your code, and our privacy and security overview for AI assistants covers that side.
The Checklist to Run Before Any Generated Code Merges

Eight checks cover the recurring failure modes. Run them top to bottom on any generated change, and stop at the first failure rather than cataloguing all of them, because one confirmed failure already means the change goes back.
| # | Check | What models predictably get wrong |
|---|---|---|
| 1 | No hardcoded secrets | Example keys and tokens pasted as real config |
| 2 | Parameterised queries only | String concatenation into SQL or shell commands |
| 3 | Input validated at trust boundaries | Happy-path parsing with no hostile-input handling |
| 4 | Every dependency verified in the registry | Invented or lookalike package names |
| 5 | Defaults reviewed for exposure | Permissive CORS, disabled TLS checks, debug flags on |
| 6 | AuthZ present on new endpoints | Routes that authenticate but never authorise |
| 7 | Errors and logs leak nothing sensitive | Stack traces and secrets echoed to clients or logs |
| 8 | Crypto uses current primitives | Weak hashes, static salts, predictable randomness |
Checks one through four catch the highest-frequency issues. Secrets and injection are the classics, and dependency verification earns its slot because invented package names are a genuinely new attack surface.
Checks five through eight catch the expensive ones. A permissive default or a missing authorisation check tends to surface as an incident rather than a bug report, which is the costliest way to learn.
What Two of the Checks Look Like in Practice
Concrete examples anchor the habit better than rules do. The query check is the fastest to demonstrate, because the dangerous version and the safe version sit one line apart.
# Generated pattern to reject: input concatenated into the query
query = "SELECT * FROM users WHERE name = '" + username + "'"
# Required pattern: parameters stay parameters
cursor.execute("SELECT * FROM users WHERE name = %s", (username,))
The dependency check is procedural rather than visual. For every import the generation added, confirm the package exists in the official registry, check its publish history looks established, and prefer the well-known name over a near-miss.
Thirty seconds per new dependency is the entire cost. Registry-squatting attacks rely on nobody spending those seconds, and the defence really is that mundane.
The exposure-defaults check rewards a similar reflex. Search any generated change for configuration keywords such as cors, verify, debug, and allow, then read each hit as a question about what just got opened. Generated scaffolding loves permissive settings because permissive settings make demos work.
How to Wire the Checklist Into an Existing Review Flow
A checklist that lives in a wiki dies in a wiki. The wiring matters more than the wording, and three attachment points cover most teams.
Put the list in the pull request template first. A visible eight-line block turns the check into a default rather than a memory feat, and reviewers tick it in under five minutes for a typical change.
Automate the mechanical half next. Secret scanners, dependency audit commands, and static analysis catch checks one, four, and parts of two without human time, and our comparison of AI code review tools looks at how far the automated layer reaches.
Then teach the assistant your rules directly. A standards file that bans string-built queries and undeclared dependencies reduces how often the checklist finds anything, and our guide to coding standards assistants will follow shows the phrasing that sticks.
Keep the human half human. Authorisation logic, trust boundaries, and data sensitivity are judgment calls, and no scanner configuration substitutes for a reader who knows which endpoint faces the internet.
Which Checklist Depth Fits Your Project

Weekend prototype with no real users: Run checks one, four, and five only. Secrets leak from abandoned repositories constantly, invented dependencies bite immediately, and everything else can wait for real traffic.
Production web application: All eight, on every generated change, without negotiation. This is the profile the checklist was built for, and the full pass costs minutes per merge.
Internal tool behind a VPN: The full list minus some intensity on check five. Exposure defaults matter less inside a private network, while injection and secrets matter exactly as much as anywhere.
Regulated or payments codebase: All eight plus named reviewer sign-off recorded in the pull request. Audit trails turn a good habit into demonstrable process, which is what assessments actually ask for.
Open source library: Weight checks four and eight heavily. Your dependency choices and crypto patterns propagate to every downstream user, multiplying both the value and the blast radius.
Team of beginners using assistants heavily: Start with checks one and two until they are reflex. A short list actually applied beats a complete list abandoned in week two.
The Shortcuts That Undo the Whole Checklist
Trusting the assistant to review its own output is the classic one. A model asked to audit code it just wrote inherits the same blind spots that produced the flaw, so self-review supplements human review and never replaces it.
Skipping the pass on small diffs comes second. A three-line generated change can disable certificate verification or widen a CORS policy, and small is precisely where reviewers relax.
Running the checklist only on new files misses the real surface. Most generated code lands as edits inside existing files, inheriting their trust context, and an edit can quietly remove a validation that the original author placed deliberately.
Reviewing faster because the code came from a premium tool deserves a mention too. Model quality shifts the odds without changing the categories, and an excellent model still cannot see which endpoint faces the internet.
The last shortcut is treating a passing scanner as a completed checklist. Scanners cover perhaps half the list on a good day, and the half they miss, meaning authorisation and boundary judgment, is where the expensive incidents live.
Make the Checklist Boring and It Will Work
Security review of generated code succeeds when it stops feeling like an event. Eight checks in the pull request template, run every time, beat any heroic quarterly audit.
The economics favour the habit heavily. Minutes per merge against incident response, disclosure, and cleanup is not a close call, and generated code volume keeps raising the stakes on the wrong side.
Adopt the list, wire it into the template, and let it be dull. Dull, repeated, and universal is what working security process looks like from the inside.
Revisit the list twice a year as tools change. New failure modes will earn slots, solved ones will retire to the scanner layer, and the discipline of a short written checklist will outlast every individual entry on it.
FAQ
Is AI-generated code less secure than human-written code?
No worse line by line, but differently distributed. Generated code concentrates on plausible-looking omissions, meaning missing validation, permissive defaults, and invented dependencies, rather than the typos human review already catches. The failure modes differ, so the checks must differ too.
Do AI assistants really invent package names?
Yes, and it is one of the most dangerous patterns. Models sometimes import packages that do not exist or that exist only as lookalike names, and attackers register those names in public registries. Every new dependency needs a registry check before install, not after.
Will a security scanner catch what AI code gets wrong?
Standard scanners catch a useful share, including known-vulnerable dependencies, obvious injection sinks, and leaked secrets. They miss logic-level gaps such as missing authorisation on a new endpoint. The checklist exists precisely for the part scanners cannot see.
When in the workflow should the checklist run?
At the pull request boundary, before merge, in the same pass as functional review. Checking earlier wastes effort on code that changes again, and checking later means the flaw already sits in the main branch. One pass per merge is sustainable, which is what makes it happen.
Which generated code needs the strictest review?
The riskiest single habit is pasting generated code that touches authentication, payment, or user data without a second reader. Boring utility code tolerates light review. Trust-boundary code written by a model deserves the full checklist every time, without exception.
Some links may be affiliate links. We may earn a commission at no extra cost to you.
This article was written with AI assistance. It is researched and fact-checked, not based on personal hands-on testing unless explicitly stated.
Comments
Post a Comment