The XSS Is Gone. The IDOR Is Everywhere.
AI-assisted development is quietly rewriting the vulnerability profile - away from injection, toward broken authorisation. That shift breaks more than our code. It breaks our tooling and our instincts.
For me, summer holidays is a time to reflect on what I have seen over the past six months, which this time have been quite dramatic themes across multiple assessments. These days I mostly assess AI-powered features in applications (AI RedTeam Assessments, "hacking the AI agents"). Whilst I do make heavy use of Claude Code, for research, building tools, and process enhancement, I am also fully aware of the impact that coding assistants, such as Claude Code and Codex, are having on the security of the applications our clients are building.
For twenty years, web application security has had a centre of gravity: injection flaws. Cross-site scripting (both stored and reflected), SQL injection, command injection - these classes dominated the OWASP Top 10, trained a generation of pentesters, and drove a billion-dollar scanner market. If you learned to break web apps, you learned to escape strings and inject code.
Since December 2025, and the dramatic uptick in AI-assisted coding, I have seen that centre of gravity move. The injection classes are receding. In their place, a few classes dominate almost every report: broken access control - IDOR, its API-era name BOLA (Broken Object Level Authorisation), inconsistent authentication and authorisation, inconsistent security boundaries, and the business-logic flaws that live next door.
The headline overstates it slightly - injection isn't extinct, other forms exist in the agent itself, and it is a long while since I have seen SQL injection. But the direction is unambiguous, and the reason for it tells you something important about what AI-assisted development is good at, what it is bad at, and why our existing defences point in the wrong direction.
Why injection is receding
Injection is, fundamentally, a library-solvable problem. You don't hand-roll HTML encoding; the templating engine escapes by default. You don't concatenate SQL; the ORM parameterises. The secure pattern is the idiomatic pattern - it's what the framework does when you use it normally.
Large language models are trained on an enormous corpus of exactly that idiomatic, framework-shaped code. So when an assistant builds a feature, it reaches for the auto-escaping template and the parameterised query by default, because that's the shape of the code it has seen a million times. The framework already solved injection; the model inherits the solution for free.
The result: the classic injection bug increasingly requires the developer (or the assistant) to go out of their way - with raw string interpolation, an unsafe render, a disabled encoder. It can still happen, especially with layered stored XSS, but it's now very much the exception.
Why authorisation issues are surging
Authorisation is the mirror image. It is not library-solvable, because it is usually application-specific. There is no framework default for "is this caller allowed this object." The rule lives in your domain, your model, your business logic - knowledge often not clear to the coding assistant.
So the assistant does what it does best: it builds the plausible happy path. getResourceById(id) fetches the resource and returns it. The code works, and is clean and functional. The one thing it omits is the question no framework asked it to consider - the business case of should this user be able to see this resource? That check isn't a syntax the model can pattern-match to; it's a semantic decision about intent, and intent is what an AI-driven coding session can loose sight of.
This is the crux, and it's worth stating plainly:
Injection is a syntax problem. Authorisation is a semantics problem. LLMs are excellent at syntax and idiom, and weak at the latent, per-application semantics nobody wrote down.
That single asymmetry explains the whole shift.
The signature: inconsistency, not absence
Here's the part that surprises teams. The problem usually isn't that authorisation is missing everywhere. It's that it's applied inconsistently - carefully on one path, weakly on the adjacent one of equal risk.
In a couple of recent assessments, a single "upload a file" operation had been implemented independently across several services. One path did content-based type validation, a size cap, and a server-generated storage path. Others did none of that and trusted the client's own filename - carrying both an unrestricted-upload flaw and a path-traversal-to-code-execution flaw. While some uploads were hardened; there were a dozen different implementations and half a dozen different security postures.
We see the same fingerprint everywhere once we look for it: sibling API endpoints using three different authorisation models; identifiers that are random and unguessable in most places but small sequential integers in others. This is not an absence of security engineering - much of it is carefully done. It's the absence of a single governing security design.
And that is the true signature of AI-assisted development at speed, each agent session can solve the same problem slightly differently. Each feature is locally plausible and works in isolation, but nothing enforces a common model across them. Speed without a governing architecture doesn't produce obviously broken code - it produces sprawl, and sprawl is where controls get missed.
Why our defences point the wrong way
The shift would be manageable if our tooling and habits moved with it. They don't yet - they point at the class that's leaving.
- Scanners are built for injection. SAST is genuinely good at tainted data flows and dangerous sinks. It is famously bad at IDOR and BOLA, because it cannot know your intended authorisation model. The tooling appsec leaned on for two decades is weakest at exactly the class now in the ascendant.
- Functional tests miss it by design. BOLA is invisible on the happy path - the feature works. Only an adversarial test ("can I fetch your object with my session?") catches it, and those are precisely the tests a single AI session writing its own tests never thinks to write.
- Code review waves it through. AI-assisted code is functional and idiomatic - which is why it passes. A reviewer scanning for something that looks wrong sees nothing wrong. The missing authorisation check is an absence, and absences don't show up in a diff.
Injection announced itself; a reviewer could learn to smell it. Broken authorisation is silent.
What actually fixes it
Point-fixing IDORs one ticket at a time loses, because the next feature just reintroduces the pattern. In an AI-assisted codebase the durable fix is to make the security design something the assistants produce and follow, backed by checks that don't depend on anyone's judgement:
- One authoritative authorisation model, written down as a reference artefact - a single shared enforcement layer every request passes through, not a decision re-made per route.
- Encode it into the assistants' standing context. A "security invariants" section in the repository's
CLAUDE.md/AGENTS.mdso new code conforms by default. Guidance artefacts are force multipliers - for good patterns as readily as bad ones. - Deterministic gates in CI, because an assistant's own judgement is a probabilistic control and must never be the enforcement boundary. Architecture tests: every object route uses the shared ownership check; every admin router carries an admin guard. One regression test per confirmed finding.
- Findings-as-guardrails. Every finding ships as a triple - an invariant, a static rule, and a test - so fixing the instance inoculates the codebase against the whole class.
The theme throughout: authorisation can't be outsourced to a library, so it has to be outsourced to architecture and enforcement. You give the assistant the model it was never born with, and you verify deterministically that it followed it.
The instinct that needs updating
The uncomfortable takeaway for our field is that the reflexes we trained for are aimed at a receding target. "Scan it for injection" is becoming the answer to yesterday's question. The question AI-assisted development actually poses is quieter and harder: is authorisation applied consistently across every path of equal risk? - and no scanner, and no happy-path test, will answer it for you.