Engineering

How to handle a security finding in generated code

Editorial · Reveneau · September 25, 2026

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.

Common questions

What is the first step when a scanner reports a problem in AI-generated code?

Decide on containment before diagnosis, by answering three questions: is the code reachable from the internet, is it running in production, and does it touch customer data. If all three are true, mitigate first (disable the endpoint, turn off the flag, restrict the route) and investigate afterwards, because understanding takes hours and exposure is happening now.

Why is a security finding in generated code different from a hand-written one?

A hand-written defect is usually one person making one mistake on one afternoon, while a generated defect comes from a pattern that repeats. If a type of prompt produced an unsafe output once, it likely produced the same output every other time that type of prompt was used, so the finding is a sample rather than an incident.

How do you find every instance of a generated vulnerability?

Search by the structure of the defect rather than its exact text: the same input-handling call, the same crypto primitive, the same missing authorisation check. Generated code varies in naming and formatting while keeping the same structure, so a literal string search finds a fraction of the real instances.

Which vulnerability classes appear most often in generated code?

The ones you cannot see when reading at normal speed, where no single line looks wrong. Veracode's Spring 2026 testing across more than 150 models found cross-site scripting passing security checks in only 15 percent of generations and log injection in 13 percent, against 82 percent for SQL injection and 86 percent for insecure cryptography.

Should you fix the flagged line or the pattern?

Fix the pattern, by writing one safe helper and making every call site use it, then removing the unsafe path so it cannot be reached again. Fixing only the flagged line leaves the other instances in place and leaves the generator free to produce the same defect in the next feature.

What check should you add after fixing a security finding?

One that fails against the vulnerable version of the code, verified by running it against that version before you merge the fix. A check added after a finding that has never been observed failing only makes people feel safe, and there is no way to know it works without testing it against the defect it was written for.

Does language choice affect how often generated code is insecure?

Materially, according to Veracode's Spring 2026 results: Java passed security checks in 29 percent of generations against 62 percent for Python, 58 percent for C sharp, and 57 percent for JavaScript. That does not mean avoid a language, but it does mean the amount of verification a team needs is different for each technology stack.

How do you decide whether a finding requires customer disclosure?

Decide on evidence rather than on hope, by determining whether the vulnerability was reachable, whether logs show it being used, and whether customer data could have been accessed. If your logging cannot answer the second question, that gap is itself a finding and should be treated as one.

Do reasoning models produce more secure code?

They perform better, and not enough to change the process. Veracode's Spring 2026 testing put the strongest reasoning models at roughly 70 to 72 percent security pass rates, which is an improvement over the overall figure of about 55 percent while still meaning a meaningful share of generations carry a defect.

What should the write-up after a security finding contain?

The structure of the defect, where else it was found, the pattern-level fix, the check that now catches it, and honestly what you still cannot rule out. Writing down what remains unknown is the part people skip, and it is the part that helps whoever handles the next finding in the same area.

How often should you scan generated code compared with hand-written code?

On every change rather than on a schedule, because AI produces new code at a rate that a scan run once a week cannot keep up with, and it misses too much between runs. The scanner running in the pipeline on every change also means the review queue is not the only check, which matters more as the volume of change rises.

Start a project