How to handle a security finding in generated code

A scanner reports a cross-site scripting vulnerability in a template that no human wrote. Or a static analysis tool finds a weak hash in a helper an agent produced three weeks ago. Somebody forwards it to you and asks what to do.
The instinct is to fix that line. It is the wrong first step, and it is wrong for a reason specific to how the code came to exist.
A hand-written defect is usually one person making one mistake on one afternoon. It is in one place. Fixing that line genuinely fixes the problem. A generated defect comes from a pattern: a type of prompt, a common coding pattern in the training data, a habit the model has. If that pattern produced an unsafe output once, it almost certainly produced the same output every other time the same pattern appeared in your codebase.
So the finding is one example of many. Treat it that way.
Where the defects actually appear
Before the process, the pattern of the risk, because it tells you where to look.
Veracode's Spring 2026 research tested more than 150 models across 80 coding tasks in four languages. Generated code compiled and ran correctly over 95 percent of the time and passed security checks in roughly 55 percent of generations. The average is less useful than the differences between classes. SQL injection passed at 82 percent, insecure cryptography at 86 percent, cross-site scripting at 15 percent, and log injection at 13 percent.
Look at which classes did well. SQL injection has an obvious pattern, it is the thing every developer has been trained on, and the string concatenation is visible in the diff. Cross-site scripting and log injection are the opposite: the defect is spread across a few lines that each look completely ordinary, and finding it requires tracking where a value came from and where it is finally used. That is the analysis a human reviewer does worst, particularly when it is the tenth review of the afternoon.
Language matters too. The same research put Java at 29 percent against Python at 62 percent, C sharp at 58 percent, and JavaScript at 57 percent. Reasoning models scored better overall, around 70 to 72 percent, which is an improvement and is still far from a rate you can accept without checking.
The practical conclusion: the findings you get will be mostly in the classes that are hardest to see by reading, and there will be more of them than a single scanner report suggests.
The five steps
1. Contain before you understand
Answer three questions in the first ten minutes. Is this code reachable from the internet? Is it running in production? Does it touch customer data?
If all three are yes, mitigate now and diagnose afterwards. Disable the endpoint, turn off the feature flag, restrict the route, allow only approved callers to use the integration. Understanding a vulnerability properly takes hours, and exposure is happening during those hours.
The cost of an unnecessary mitigation is a feature being unavailable for an afternoon. The cost of understanding first is measured in whatever the vulnerability lets somebody do. Those are not comparable and the decision should be easy.
If the answer to any of the three is no, you have time. Use it properly rather than rushing.
2. Find every instance
This is the step that separates handling a generated finding from handling a hand-written one, and it is the one people skip.
Search by the structure of the defect, not its exact text. Generated code varies its naming and formatting while keeping the same structure, so a literal string search finds only a few of the real instances. Search for the same input-handling call. The same crypto primitive. The same missing authorisation check before a database write. The same unescaped interpolation into a template.
Then search more widely. If the defect is unescaped user input reaching a log line, look at every place user input reaches a log line, whether or not a scanner flagged it. Scanners miss their own kinds of problems, and those are different from what a reviewer misses, which is an advantage and also a limit.
Write down the count before you fix anything. That number is the honest measure of the problem, and it is the number that determines whether this is a fix or an incident.
3. Fix the pattern, not the lines
One safe helper. One place. Every call site uses it.
Then remove the unsafe path so it cannot be reached again: delete the raw function, make it private, or add a lint rule that rejects direct use. If the unsafe way remains available, the next code the AI writes will use it, because the model is using the same coding patterns that produced the defect the first time.
This is more work than patching the reported line and it is the only version that lasts. Patching individually leaves you doing the same investigation again in six weeks with a different scanner report.
4. Add the check that would have caught it
If you fix the code and add nothing, you have only delayed the problem by a few weeks.
Write a check that fails against the vulnerable version. Then actually run it against the vulnerable version and confirm it fails, before you merge the fix. This sounds overly careful and it is the difference between a real check and one that only makes people feel safe. A check written after a finding, that has never been observed failing, is an assertion about your own carefulness rather than a property of the system.
We keep coming back to this because it is the most common way verification goes wrong. Inherited and generated suites frequently contain assertions that would pass against an empty implementation, and because every check passes nobody has questioned them. The list is in when evals give false confidence, and the method for building checks from the specification rather than from the code is in how to write your first eval suite.
Put the check in the pipeline, not on a schedule. At current generation volumes, a weekly scan misses too much in between.
5. Decide what to disclose, on evidence
Three questions determine this. Was the vulnerability actually reachable, given the routing and authentication in front of it? Do the logs show it being used? Could customer data have been accessed?
Answer them from data, not from what you would prefer. And if your logging cannot answer the second question, that is itself a finding and should go on the list, because "we do not know whether anyone used it" is a much worse position to be in than "we checked and nobody did".
Regulated contexts have their own timelines and thresholds, and those are legal questions rather than engineering ones. Collect the facts quickly so whoever makes that decision is deciding on evidence. The related practice for regulated builds is in building an audit-ready codebase.
What to change in the process afterwards
Findings are expensive information. Use them properly.
Scan on every change. Not weekly. The volume of generated change is high enough that a periodic scan leaves too long a gap between scans, and the pipeline also means the review queue is not the only check. That queue is under enough pressure already, as we argued in the review queue is now the slowest step.
Give the reviewer a shorter list. The OWASP Top Ten is the standard reference and it is too long to remember during a review. Pick the three or four classes your product is genuinely exposed to and make those explicit review questions. For most web products handling user content, injection into templates and logs belongs on that list based on the Veracode numbers alone.
Constrain what agents can reach. A generated defect is only exploitable if the code has real permissions. Limiting what an agent-written component can access limits how much damage a defect you have not discovered yet can cause, which is the only kind of protection that works against unknowns. The practical version is in AI agent permissions and guardrails.
Write it down honestly, including the unknowns. The structure of the defect, where else it was found, the pattern-level fix, the check that now catches it, and what you still cannot rule out. That last section is the one people leave out and the one that helps the next person.
Thanks to the security engineers who explained to us findings that turned out to be in many places rather than one. The pattern was consistent: the scanner reported one instance, the search by structure found between six and forty, and nobody had thought to look until someone asked.
A finding in generated code is rarely a single bug. It is the first instance you found, and the others are still in the code.
Related guide: Is AI-generated code secure?
Sources
- Veracode, Spring 2026 GenAI Code Security update: 150+ models, 80 tasks across four languages; over 95 percent syntactic correctness against roughly 55 percent security pass rate, with SQL injection at 82 percent, insecure cryptography at 86 percent, cross-site scripting at 15 percent, log injection at 13 percent; Java 29 percent, Python 62 percent, C sharp 58 percent, JavaScript 57 percent; reasoning models 70 to 72 percent.
- OWASP Top Ten: the standard reference list of web application security risks.


