The Security Question Your Scanner Can't Ask
Access control tops OWASP's 2025 list again because authorization is business logic, and no tool audits that for you.
When OWASP published the 2025 edition of its Top 10 last November, Broken Access Control held the #1 spot for the second cycle in a row — and got bigger, swallowing server-side request forgery as a sub-category. The numbers behind the ranking are blunt: 100% of applications in the contributed dataset showed some form of broken access control, with 1.8 million recorded occurrences across 40 CWEs. Meanwhile injection, the monster that an entire generation of secure-coding training was built around, slid to #5 — down from #3 in 2021 and #1 in 2017.
Those two trend lines, read together, tell you what kind of problem backend security has become. And they explain why the security instinct that actually matters for backend engineers has almost nothing to do with cryptography.
Mechanism problems get solved. Decision problems don't.
Injection is fading because we moved the fix into the mechanism. Parameterized queries ship as the default in every serious ORM and driver; you now have to go out of your way to concatenate SQL. Password storage went the same way — Argon2id or bcrypt behind one library call. Authentication is a single, heavily trafficked path, usually owned by a framework or an identity provider, hammered by every user on every login. Solve it once, centrally, and it stays solved.
Authorization is a different kind of problem. "Can this user see invoice 123?" isn't a mechanism — it's a business rule, and it has to be re-decided at every endpoint, every queue consumer, every GraphQL resolver, every internal admin tool. There's no library call, because no library knows that invoices belong to accounts and accounts belong to tenants. Authentication is one door; authorization is every door, and each one is guarded — or not — by whoever wrote that handler on a Thursday afternoon.
That asymmetry is why the failure mode is so persistent. In 2023, CISA, the NSA, and Australia's ACSC took the unusual step of publishing a joint advisory about insecure direct object references — a bug class old enough to drink — because attackers were still walking through sequential IDs and exfiltrating personal and financial data on millions of users. OWASP's separate API Security Top 10 reaches the same verdict: broken object-level authorization sits at #1 there too.
Why your scanner will never catch it
Here's the uncomfortable part for anyone whose security posture is "we run SAST in CI": code with an authorization bug is syntactically identical to code without one.
# Both of these look fine to a scanner.
# One of them leaks every invoice in the system.
invoice = db.get(Invoice, invoice_id)
invoice = db.get(Invoice, invoice_id, owner_id=current_user.account_id)
A static analyzer can flag string-built SQL because the vulnerability is visible in the syntax. It cannot know your domain model. Dependency scanners, secret scanners, fuzzers — all mechanism auditors. None of them can answer "should this caller be allowed to do this?", which is precisely the question behind the #1 vulnerability class. Ask anyone who triages a bug bounty program what fills the queue: it's not exotic crypto breaks, it's people changing an ID in a URL.
The industry's emerging answer is authorization-as-code: Open Policy Agent, AWS's Cedar language (the engine under Verified Permissions), and Oso. These are production-ready and genuinely worth adopting for one reason above all: a centralized policy is a reviewable, testable artifact. Diffing a policy file in a PR beats grepping 200 handlers for a missing WHERE tenant_id =. But be clear-eyed about the limit — you still have to call the policy engine at every enforcement point, and a forgotten call is the same bug in a nicer jacket. Policy engines relocate the problem to somewhere auditable; they don't dissolve it.
The pre-ship self-audit
The habit that separates engineers who ship these bugs from engineers who don't is a short list of hostile questions, asked while reading your own diff. Before merging a backend change, walk it:
- Every identifier in the request — path param, body field, JWT claim: what happens when someone substitutes a value that isn't theirs? Is scoping enforced in the query itself, or filtered after the fetch (or worse, client-side)?
- Every endpoint — what does a regular authenticated user get from the admin route? "It's not linked in the UI" is not an answer.
- Every token — what can't you revoke right now, and for how long? A stateless JWT is valid until expiry no matter what your database says. And remember signed is not encrypted: anything in the payload is readable by whoever holds it.
- Every rate limit — where does the counter actually live once you're running twelve replicas? A per-process in-memory limiter multiplied across pods is a rate suggestion. Per-IP alone falls to botnets; you want per-account layered on top.
- Every secret — can you rotate it without a deploy? If rotation requires a code change, you won't rotate under pressure.
These read like interview questions, and that's the indictment: the industry treats abuse-case thinking as a hiring signal while giving it no artifact in the shipping pipeline. Fix that by turning abuse cases into tests. Create two tenants in a fixture, then assert 403 or 404 for every cross-tenant access on every resource route. It's boring, mechanical, cheap to write — and it's a regression net for the single most common vulnerability class in the world. Almost nobody does it.
The instinct you can't install
My prediction: Broken Access Control tops the 2029 list too. Not because the problem is hard in any deep sense, but because it's diffuse, and the industry keeps reaching for centralized tooling to fix a decentralized habit. The tooling helps — adopt a policy engine if your authz logic has outgrown if user.id == resource.owner_id. But the durable fix is a reading habit: treat every input as hostile, and for every line that touches data, ask who this trusts and why. It's the highest-leverage security skill a backend engineer can build, and the one thing in your stack you can't npm install.
Sources & further reading
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 0
No comments yet
Be the first to weigh in.