We security-test a lot of software that was built with AI tools. People expect us to report exotic failures, on the theory that a strange author produces strange bugs.
It is almost never exotic. The same handful of gaps turn up again and again, they are all well understood and every one of them looks completely reasonable sitting in a code review.
That last part is the interesting bit, so it is worth starting there.
The shared cause
A language model produces the most plausible code for the request it was given. Plausible is doing a lot of work in that sentence. It means the code looks like the code that usually appears in this situation, because that is what the model learned from.
Most code in the world implements the intended path. Comparatively little of it defends against the unintended one. So the output leans, consistently and predictably, towards software that does what was asked and does not consider what else could be asked of it.
This is not the model being careless. It answered the question. The question was how to let a user download their invoice. Nobody asked how to stop a user downloading somebody else's.
Everything below follows from that.
What we actually find
Authorisation that checks the wrong thing. Far and away the most common. The application correctly confirms that you are logged in, then fetches the record by the identifier in the request without confirming the record is yours. Change the number in the URL, get somebody else's data. It is the oldest flaw in web applications and it survives because "is this user logged in" and "is this user allowed this record" look like the same check until you say them out loud.
Secrets in the source. API keys, connection strings, a token for the payment provider. Often they arrived during a debugging session, worked and stayed. They then travel wherever the code travels, which increasingly includes a repository somebody made public without thinking about it.
Errors that explain too much. The stack trace renders to the browser. It names the framework, the version, the database, the file path and occasionally the query. None of that is a breach by itself. All of it shortens the reconnaissance for somebody who intends one.
Uploads that trust the file. The extension is checked, the actual content is not and the destination is somewhere the web server will happily execute. The AI wrote a working upload feature. A working upload feature is not the same thing as a safe one.
Rate limiting nobody thought about. The login form accepts unlimited attempts. The password reset endpoint can be called forever. The expensive AI-backed search is free to hammer, which is a denial-of-service problem and an invoice problem simultaneously.
Dependencies chosen by vibe. The library that fit the pattern, at whatever version was current in the training data. Sometimes that version has a known advisory. Sometimes the package is simply abandoned.
Why it survives review
Because none of it looks wrong.
Human-written code carries tells. Odd naming, an inconsistent shortcut, a comment that admits something. Reviewers have spent careers learning to slow down at those moments.
AI-written code has no tells. It is uniformly tidy, consistently named, sensibly structured and often better commented than the code around it. It reads as though a competent person wrote it carefully, which is precisely the impression that makes a reviewer skim.
The flaw is not hidden. It is sitting in plain sight, looking exactly as reasonable as everything either side of it.
What changes the outcome
Not writing less code with AI. The productivity is real and it is not going back.
What changes the outcome is having somebody in the process whose job is to be adversarial about the result: to ask what else this endpoint would answer, what happens when the field is empty, what happens when the identifier belongs to somebody else. That is a different discipline from building. It is the one most often missing when software is produced quickly by one person with a capable tool.
Code that works and code that is safe are two separate tests. Only one of them passes by accident.