AI Security · Apr 8, 2026 · 6 min read

AI-generated code is your newest attack surface

Over 92% of teams use AI coding assistants, and AI-written code is now a top AppSec blindspot: new dependencies, new sink flows, embedded secrets.

More than nine in ten engineering teams now ship code an assistant helped write, and the assistant does not get tired, does not slow down, and does not stop to ask whether the pattern it just produced is safe. The result is not a new class of vulnerability so much as a new volume of an old one: the same insecure shape, an unparameterized query, a hardcoded token, a missing authorization check, stamped out across dozens of files in a single afternoon. The attack surface did not get more exotic. It got bigger and more repetitive at the same time.

That changes the economics of review, not the nature of the bug. A human writes one vulnerable handler and a reviewer might catch it. An assistant writes fourteen handlers from the same prompt, all sharing the same flaw, faster than any reviewer reads. The honest answer is not to ban the assistant. It is to put the assistant's output through exactly the proof bar you already apply to a human's, and to make that bar scale with the volume.

92%+
teams using AI coding assistants
3
doors risk arrives through
1
vulnerability per file:line, deduped

01Three doors, all open at once

AI-written risk does not arrive through a single channel. It arrives through three at the same time, and a tool that only watches one of them misses most of it. The first door is dependencies: the assistant pulls in an open-source package to satisfy an import without anyone deciding to trust it. The second is taint flow: generated logic wires user input into a sink that no human chose to connect. The third is secrets: a credential gets pasted, or invented, straight into the source.

Each door needs a different oracle. A dependency is a question about provenance and reachability. A taint flow is a question about source-to-sink data movement. A secret is a question about what literally sits in the file and the git history behind it. apPosture watches all three and, critically, refuses to treat any of them as a finding on appearance alone.

02The codegen burst and the repeated sink

The signature of AI-generated risk is repetition. One prompt produces many near-identical sink sites, and a pattern matcher reacts to each of them as if it were a separate discovery. That is how a single bad habit becomes a wall of forty rows that a reviewer stops reading at row five.

prompt: "add CRUD endpoints" ──► AI codegen burst ┌──────────────┬───────────┴───┬──────────────┐ orders.py:41 users.py:58 invoices.py:33 audit.py:77 f"...WHERE id={id}" f"...WHERE id={id}" f"...={id}" f"...={id}" pattern match ──► 4 noisy rows, same antipattern, no proof apPosture: taint source (request param) ──► sink (raw query) proven reachable on orders.py:41 ──► 1 finding, 3 correlated
One prompt, one antipattern, four files. Pattern matching raises four lookalike rows; apPosture proves the flow reaches a real query boundary and collapses the rest by class and location.

The difference is not cosmetic. A pattern matcher tells you a string looks like a SQL concatenation. Inter-procedural taint tracking tells you that a value from an HTTP parameter actually flows, across function and file boundaries, into a query that hits the database. The first is a guess about syntax. The second is a fact about data movement, and only the second earns a place at the top of a queue.

03Pattern match versus proven sink

The distinction is worth making concrete, because it is the whole game. Two engines can look at the same generated function and reach opposite conclusions about whether it is worth a human's attention.

DimensionPattern match (noisy)Proven sink (apPosture)
SignalCode resembles a known-bad shapeTainted input reaches a real sink across files
EvidenceRegex or AST shape matchSource-to-sink taint path with a reachable boundary
Repeated codegenN lookalikes, N separate rowsOne finding per file:line, siblings correlated by class
Reviewer costTriage every row by handRead the proof, fix the root pattern once
Failure modeFalse positives drown the real oneShorter list, every item survives audit

Proving the sink is what lets the volume problem solve itself. When the engine knows a flow is real, it also knows which of the forty lookalikes share the same weakness class and the same root cause, and it can say so without pretending each one is a fresh incident.

04One fingerprint per generated antipattern

Dedup is where the repetition stops being overwhelming. apPosture gives every code weakness a stable identity built from its weakness class and its exact source location: vuln_class | file:line. The class comes from the CWE when one is known and falls back to a normalized name; the location is the file and line where the sink lives.

code finding ──► fingerprint = v{N} | code | app | vuln_class | file:line orders.py:41 sqli ──► 89 | orders.py:41 distinct vuln users.py:58 sqli ──► 89 | users.py:58 distinct vuln orders.py:41 sqli ──► 89 | orders.py:41 same key ──► collapses
The same antipattern at the same line, seen twice by two scans or two tools, collapses to one Vulnerability. The same antipattern at a different line stays its own unit of work, because that is a different fix.

This is the right granularity for generated code. You do not want forty rows for one prompt, and you also do not want one row that silently hides thirty-nine distinct fixes. Keying on file:line means a repeated SQL injection in four files surfaces as four addressable units, while the same finding re-reported by a second scan or a second tool merges into the record you already have. The signal stays high without losing a single place a developer has to edit.

05Same proof bar, regardless of author

None of this treats AI-written code as a special category, and that is the point. The engine does not ask who wrote the line. Inter-procedural taint tracking follows the flow, SCA checks every newly pulled dependency for reachability and malicious-package signals, and secrets scanning reads both the current head and the git history behind it. A generated handler that passes user input into a query is a real flow, not a style nit, and it clears the same bar a human's handler would.

The loop closes in the pipeline. The assistant proposes, the engine proves, and the gate blocks net-new risk before it reaches a release. Because findings carry proof rather than suspicion, the gate can fail closed without burying the team in noise: the list it hands back is short, deduped to one unit per real fix, and every item on it survives the same audit a human reviewer would have demanded. AI made the volume of code go up. It does not get to make the standard of evidence go down.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →