Programming has always been about translating human intent into something a machine can execute.
A digital computer works with electrical states that are modeled as 1 and 0. That is precise for the machine, but painful for humans. Assembly made the interface slightly better by giving humans mnemonics and labels, but the programmer still had to describe small machine operations by hand. Moving a value, setting a register, jumping to an address, comparing a condition, and managing memory all had to be expressed close to the processor’s view of the world.
That did not scale to the number of people and systems software needed to support.
The usual engineering response was to add another layer. Compilers let humans express intent in a language like C and then translate it into lower-level machine behavior. This made software development more accessible and productive. It also moved the programmer one step away from the machine.
That distance is where many bug classes live.
C gives programmers direct control over pointers, object lifetimes, buffers, integer sizes, and memory layout. That control is useful for systems work, but it leaves less room for automatic checking. A small mismatch between what the programmer intended and what the code actually does can become a buffer overflow, use-after-free, integer overflow, format string bug, or type confusion.
The next layers made programming easier again. Java, C#, PHP, Python, JavaScript, and their frameworks raised the abstraction level. They removed many sharp edges from manual memory management, but they introduced different forms of ambiguity: implicit type conversion, serialization behavior, runtime reflection, framework magic, templating behavior, ORM assumptions, authorization middleware, and dynamic dispatch.
A PHP developer may intend to compare two values, but loose comparison can turn a security check into a type-juggling problem. A JavaScript developer may intend equality and accidentally choose == where === would preserve type. A .NET developer may intend to deserialize a safe object, but the deserialization framework may invoke callbacks, delegates, or type resolution paths before application logic gets control. A web developer may intend to protect a feature with a framework-level check, but the actual bug may sit in the business workflow around that check.
These bug classes became familiar over time. Security tools, code scanners, exploit mitigations, framework defaults, secure coding guides, and developer education were built around them. The industry learned to look for memory safety issues in low-level code and injection/deserialization/access-control issues in higher-level applications.
Agentic coding adds another layer.
Five eras of system interaction
A simplified way to view programming history is through the interface humans use to express intent.
| Era | Interaction model | Main benefit | Common ambiguity layer |
|---|---|---|---|
| 1 | Machine code | Exact machine behavior | Almost no human-friendly abstraction |
| 2 | Assembly | Symbolic machine instructions | Human must still think like the CPU |
| 3 | Low-level languages such as C and C++ | Portable systems programming | Memory, lifetime, integer, and undefined-behavior gaps |
| 4 | High-level languages and frameworks | Faster application development | Runtime, framework, type, serialization, and data-flow gaps |
| 5 | Natural language with coding agents | Human intent becomes the interface | Domain, specification, authority, and verification gaps |
The graph is not a measurement. It is a mental model. As the interface becomes easier for humans, the expression becomes less formal. In the machine-code era, the human difficulty is high but the machine instruction is explicit. In the natural-language era, the human difficulty drops, but the system has to infer much more from vague intent.
This is why the statement “AI writes secure code” needs a boundary around it.
A coding agent may be good at avoiding known era 3 and era 4 mistakes. It may choose safer library calls, avoid obvious SQL injection, add input validation, generate tests, and follow familiar secure coding patterns. That is useful. But it does not mean the resulting system is secure, because the programmer has moved to a new interface.
The new interface is natural language.
Natural language is easier for humans, but it is not a strict programming language. A prompt can omit domain rules, edge cases, threat assumptions, tenant boundaries, failure behavior, and abuse cases. The agent will still produce code. The dangerous part is that the output may look clean, typed, tested, and idiomatic while implementing the wrong intent.
The new bug class is not only code quality
Era 5 does not replace era 3 and era 4 bugs. It sits above them.
Agents still generate C, C++, Java, Python, JavaScript, YAML, SQL, shell scripts, Terraform, Dockerfiles, and CI workflows. All the old bug classes still matter. A generated C parser can still have memory corruption. A generated web route can still have SQL injection. A generated Node.js service can still misuse eval, prototype behavior, or authorization middleware.
The difference is that a large class of failures now starts before the programming language.
The bug begins in the intent layer.
Some examples:
- The prompt says “add admin approval” but does not define whether approval is per user, per organization, per device, per transaction, or per time window.
- The prompt says “do not allow duplicate refunds” but does not specify concurrency, retries, partial failures, or idempotency keys.
- The prompt says “users can only see their own records” but does not define how tenant identity is derived across background jobs, exports, caches, and support tooling.
- The prompt says “validate uploaded files” but does not define parser behavior, archive extraction rules, MIME confusion, nested files, or post-upload processing.
- The prompt says “make the test pass” and the agent changes the implementation, the test, or the mock in a way that preserves green output while removing the security property.
These are not classic syntax mistakes. They are domain-model mistakes.
The code may be idiomatic. The linter may pass. The type checker may pass. The unit tests may pass. The vulnerability is that the system faithfully implements an underspecified or wrong model of the business process.
Security issues that become more important in era 5
The following issues are not all new. Many existed before agents. What changes is their frequency and shape when natural language becomes the programming interface.
| Issue class | What fails | Example |
|---|---|---|
| Missing domain rules | The prompt omits a business invariant | A refund flow checks whether an order exists but not whether the order was already refunded through a different channel |
| Missing edge cases | The happy path is implemented cleanly, but boundary behavior is unspecified | Retry logic creates duplicate actions after timeout because idempotency was not part of the prompt |
| Authorization model drift | The generated code checks one boundary but misses another | User-facing route checks tenant ID, but export, cache warmup, or admin helper code does not |
| Spec-to-code mismatch | The agent implements a plausible interpretation, not the intended one | “Inactive account” means disabled login to the business, but the generated code treats it as no recent activity |
| Test laundering | Tests are generated from the same weak assumption as the implementation | The tests prove the agent’s interpretation, not the security property |
| Tool authority confusion | The coding agent changes more than the user intended | A refactor touches CI, dependency scripts, config, database migrations, or infrastructure defaults |
| Transitive setup risk | Untrusted project instructions become tool execution | A repository setup step, package script, or error message steers the agent into running commands with developer authority |
| Policy bypass through abstraction | Framework defaults are trusted without checking the real flow | Middleware protects routes, but async jobs, webhooks, importers, or generated GraphQL resolvers bypass the same check |
| Hidden dependency behavior | The agent selects libraries or patterns without explaining operational risk | A package brings install scripts, broad permissions, unsafe deserialization, or network behavior |
| Overbroad remediation | The agent fixes the visible symptom while weakening the security boundary | Rate limiting is added to one endpoint while the underlying object-level authorization flaw remains |
This is why domain knowledge becomes more important, not less.
If the agent can write the language syntax, the scarce skill shifts toward knowing what the system must preserve. In medical software, that means understanding patient data boundaries, clinical workflow, audit, and safety rules. In retail, it means inventory, refunds, coupons, fraud, loyalty points, payment flows, and abuse paths. In manufacturing, it means process state, safety interlocks, downtime, machine identity, operator roles, and recovery behavior.
A person who understands only the programming language may accept clean-looking code. A person who understands the domain can ask whether the generated code preserves the real invariant.
The old security tooling is necessary but not sufficient
Most secure coding tools were built around era 3 and era 4 assumptions.
Static analysis is good at finding certain memory, injection, taint, API misuse, and configuration patterns. Dependency scanners are useful for known vulnerable packages. Linters and type checkers catch many implementation mistakes. Fuzzers are powerful when input surfaces and oracles are clear.
These tools still matter.
But an era 5 failure may not look like a dangerous function call. It may look like a clean implementation of an incomplete requirement.
A scanner may not know that a refund should be impossible after settlement. A type checker may not know that a support user should see metadata but not raw patient records. A unit test may not know that a failed manufacturing command must leave the machine in a safe recovery state.
For these bugs, patching the generated code is often the second step. The first step is writing down the rule the system was supposed to protect.
Specs are becoming part of the security boundary
This explains the renewed interest in spec-based development, plan modes, and structured agent workflows.
Plan modes in coding assistants force the system to pause before editing. Spec-driven workflows try to turn vague prompts into requirements, design, tasks, and acceptance criteria. Plan skills such as grill-me can force the agent to ask harder questions before it writes code.
These are useful because they narrow the gap between human intent and machine output.
But a plan is not enough by itself. A plan written by the same model can carry the same missing assumptions as the code. The security value comes when the plan externalizes assumptions so they can be reviewed, challenged, tested, and enforced.
The useful pattern is:
natural language intent
-> explicit domain rules
-> threat assumptions
-> acceptance criteria
-> implementation
-> independent verification
The important word is independent.
Automated reasoning will matter more
In the agentic coding era, automated reasoning and formal methods become more practical because the bottleneck moves from typing code to specifying properties.
Not every application needs full formal verification. Most teams will not write a complete mathematical model for every feature. But smaller forms of automated reasoning can still help:
- Type systems can make illegal states harder to represent.
- Property-based tests can check behavior across generated inputs instead of a few examples.
- State-machine tests can verify flows such as checkout, refund, onboarding, device enrollment, or workflow approval.
- Contract tests can check service boundaries and API assumptions.
- Policy-as-code can express authorization rules outside scattered route handlers.
- Static analyzers can check taint, dangerous API use, dependency behavior, and configuration drift.
- Model checking can test whether a state machine violates an invariant under unusual interleavings.
- Differential tests can compare generated behavior against an older implementation, a reference model, or a patched version.
This fits agentic coding well because agents are good at producing candidate implementations, test scaffolds, and models. That does not make the model the judge. Let the model propose the change, then let tests, policies, traces, types, or another verifier decide whether the change holds.
That mechanism can be a test suite, a type checker, a policy engine, a model checker, a sandbox, a trace, a human domain review, or a separate verifier with a stricter task.
What security review should look like now
Reviewing agent-written code only at the generated diff is too late. The earlier question is what intent produced the diff and which security property the change was supposed to preserve.
Useful questions:
- What domain rule is this change supposed to preserve?
- Which edge cases were not stated in the prompt?
- Which actor, tenant, role, device, account, or workflow boundary matters here?
- What did the agent assume without asking?
- Did the tests verify the real security property or only the generated behavior?
- Did the agent change tooling, dependencies, scripts, CI, migrations, infrastructure, or permissions outside the expected code path?
- What independent check would catch a wrong-but-plausible implementation?
This is a different review posture from classic secure code review. The reviewer still needs to know code, but the first object of review is the specification gap.
Takeaway
Agentic coding lowers the barrier for expressing intent to machines. That is a real shift. More people can build software, and experienced developers can move faster.
But every abstraction layer creates its own failure modes. Machine code made human expression hard. Assembly made it slightly easier. C made systems programming portable and powerful, while exposing memory and lifetime mistakes. High-level languages removed many low-level hazards while introducing framework, runtime, and data-flow mistakes.
Natural language is the next wrapper.
The security problem in this era is not only whether AI can write memory-safe or injection-free code. The harder question is whether the human expressed the right domain rule, whether the agent preserved it, and whether an independent mechanism verified it.
The advantage moves toward people who understand the domain deeply enough to specify the right invariants and review the generated system against them.