A junior developer once shipped a login endpoint in an afternoon, and nobody blinked. Today an assistant writes that same endpoint in eleven seconds, and the diff looks just as confident. That confidence is the problem, because generated code carries the shape of correctness without any of the accountability that used to come attached to it.
Teams that adopt AI pair programming rarely fail because the model writes nonsense. They fail because the model writes something plausible, the reviewer skims it, and a quiet flaw slips into production wearing clean formatting. The fix is not to stop generating code; it is to review it with a checklist built for the specific ways machines get security wrong.
What follows is that checklist. It assumes you are busy, that the diff is longer than you would like, and that you need to know where to look first.
Start With the Trust Boundaries
Before you read a single line, find the places where data crosses from somewhere you do not control into somewhere you do. Request handlers, webhook receivers, file uploads, message queue consumers, and anything parsing a query string all qualify. Models are excellent at wiring these paths together and noticeably worse at defending them, so the boundary is where your attention buys the most.
Look for validation that exists in name only. A schema that accepts a string of any length, a regular expression that matches too generously, an integer parsed without a range check: each one reads like diligence and provides almost none. The weaknesses behind most real world exploits are boring and repetitive, which is why the CWE Top 25 Most Dangerous Software Weaknesses list remains the fastest way to calibrate what you are hunting for.
Read Every Authentication and Authorization Path Twice
Authentication bugs are the ones that end careers, and they are the category where generated code is most likely to produce something that works in the happy path and collapses everywhere else. Check that the session token is verified on the server, not merely decoded. Check that expiry is enforced. Check that a signature algorithm is pinned instead of read from the token itself.
Authorization is the subtler half. A model will often protect a route and then forget that the object behind the route belongs to a specific user. If the handler loads a record by an id taken from the request and returns it without asking whether the caller owns it, you have an access control flaw regardless of how solid the login flow looks. Test it by changing one identifier in a request and seeing what comes back.
Audit the Dependencies the Model Chose for You
Generated code arrives with imports, and those imports were selected by a system optimizing for familiarity rather than maintenance status. You will see packages that peaked years ago, packages with a single maintainer, and occasionally packages that do not exist at all, which is its own attack surface once someone registers the name.
Run the audit before you read the logic. Tools such as npm audit resolve the whole tree against published advisories and tell you which transitive dependency is actually responsible, which saves you from upgrading the wrong thing. Whatever your ecosystem, the equivalent command belongs in the review, not in a quarterly cleanup.
Then ask something the tooling cannot answer: does this project need the dependency at all. A twelve line utility pulled in as a package is a supply chain relationship you now own forever.
Hunt for Secrets Before They Reach a Commit
Hardcoded credentials show up in generated code constantly, partly because examples in training data are full of them. Sometimes it is an obvious placeholder, and sometimes it is a real key the model reproduced from a config file you pasted into the chat window three prompts earlier.
Scan rather than skim. Detection tooling like Semgrep Secrets combines pattern matching with entropy analysis and dataflow tracking, so it catches the key that got renamed into an innocent looking variable two files away. Grep alone will not follow that trail.
Make the Review Continuous, Not Ceremonial
A checklist applied once at merge time protects you against the code you happened to read. It does nothing about the dependency that became vulnerable last Tuesday or the endpoint an assistant refactored while you were on vacation. Static review and continuous oversight solve different halves of the problem, and skipping the second half is how small issues become long dwell times, a dynamic explored well in this breakdown of why cyber resilience now requires constant monitoring.
Put the checks where they run without anyone remembering them. Dependency audits in the pipeline, secret scanning on every push, and a linter configured to fail the build rather than emit a warning nobody reads. Teams asking is vibe coding secure? usually discover the answer depends far less on the model and far more on what their pipeline refuses to let through.
The Reviewer Still Owns the Code
Speed is real and worth having. An assistant can produce in an hour what used to take a sprint, and pretending otherwise helps nobody. What has not changed is that somebody signs off, and the signature means the same thing it always did.
Treat generated code the way you would treat a contribution from a talented stranger who has never seen your threat model. Read the boundaries, verify the permissions, question the imports, scan for secrets, and automate everything you would otherwise forget. It is a short list, and it holds up under deadline pressure, which is the only real test of a security practice.
The teams that stay out of trouble are not the ones generating less code. They are the ones that decided early what a review has to prove, and then refused to merge anything that could not prove it. See more.
