Your API's biggest hole isn't injection - it's the ID in the URL
Broken Object Level Authorization has been OWASP's #1 API risk across two consecutive editions, and it's invisible to WAFs, schema validators, and most scanners - because the request is perfectly well-formed. Here's why BOLA is structurally different from injection, and what actually stops it in 2026.
GET /api/invoices/1042 returns your invoice. You're logged in, the token is valid, the response is a clean 200. Change one character - GET /api/invoices/1041 - and it returns someone else's invoice. Same token. Same session. No error, no warning, no rate limit tripped. The endpoint correctly checked that you're authenticated. It never checked whether you're authorized to see invoice 1041 specifically.
That gap - between "this caller is who they say they are" and "this caller may act on this exact object" - is Broken Object Level Authorization, and it is the single most common serious flaw in APIs today. It's not a new bug class. It's the API-era name for IDOR (Insecure Direct Object Reference), a vulnerability web apps have had since the early 2000s. What's changed is the blast radius: APIs expose object IDs directly and constantly, on every mobile app, every SPA, every partner integration, every webhook - and BOLA has topped the OWASP API Security Top 10 across two consecutive editions as a result.
This piece is about why BOLA keeps winning against the tooling that's supposed to catch it, and what actually closes the gap in production.
What OWASP actually says
The current OWASP API Security Top 10 (2023 edition) lists it as API1:2023 - Broken Object Level Authorization, in the #1 slot, the same position it held in the 2019 edition. OWASP's own description is precise: object level authorization is an access-control check implemented at the code level, per endpoint, to confirm that the specific logged-in user may perform the specific requested action on the specific object referenced by an ID in the request. Miss that check on any endpoint that accepts an object ID, and you get unauthorized read, write, or delete access to other users' data.
It's worth being exact about the neighboring risk too, because people conflate them: API3:2023 is Broken Object Property Level Authorization - field-level exposure (a response leaking an internal is_admin field, or a request letting a caller set fields they shouldn't). BOLA is about the object as a whole: can you reach it at all. This article is about API1, not API3.
Industry telemetry backs up the ranking: security vendors report BOLA present in roughly 40% of observed API attacks - not a niche finding, the dominant pattern.
Why this isn't "just injection with extra steps"
It's tempting to bucket BOLA with SQL injection and XSS as "another input validation problem." That framing is wrong, and the difference is exactly why BOLA survives so much security tooling.
Classic injection bugs share a signature: the input itself is malformed or malicious. A '; DROP TABLE users;-- in a form field is syntactically abnormal. A <script> tag in a comment box is abnormal. That abnormality is what a WAF pattern-matches on, what a schema validator rejects, what a fuzzer's mutation strategy is built to surface. The defense and the attack live in the same layer: the shape of the input.
BOLA has none of that shape. Walk through the request that leaks invoice 1041:
Authentication succeeds. The token is real, unexpired, correctly signed.
The request is syntactically valid.
GET /api/invoices/1041is a well-formed path with a well-formed integer parameter. It matches the OpenAPI schema perfectly.The response is a clean 200 with a normally-shaped JSON body.
There is no malformed anything anywhere in this transaction. The only thing wrong is a fact that lives entirely in the business logic: invoice 1041 belongs to a different tenant, and nobody wrote the line of code that checks that before the database query runs. A WAF has nothing to pattern-match. A schema validator has nothing to reject - the request is the schema. This is why BOLA is often filed under the broader "broken access control" family that consistently tops the general OWASP Top 10 for web applications too, not because it's an edge case, but because access control failures are structurally the failures that look identical to legitimate traffic.
Why scanners miss it
Automated API scanners are good at exactly the things injection defenses are good at: malformed input, unexpected types, boundary conditions, known payload signatures. They are, as several security researchers have put it, glorified syntax checkers - and syntax checking cannot answer "does user A own object 1041."
That answer requires information a generic scanner has no access to: your data model's ownership and tenancy relationships. Who owns what, which roles can act across which boundaries, whether "team" scoping or "organization" scoping or "account" scoping is the unit that matters for this particular resource. None of that is visible in an OpenAPI spec or an HTTP response. A scanner hitting /api/invoices/1041 with a valid token gets a 200 back and, absent a second identity to compare against, has no way to know that 200 is wrong.
That's the actual mechanism, concretely:
Real BOLA testing needs two or more authenticated identities - account A and account B, each with their own resources - and a systematic pass trying account A's token against account B's object IDs (and vice versa).
That kind of cross-account replay is something dedicated API security testing tools can do if they're pointed at your object relationships (built from your OpenAPI spec plus observed traffic), or something a human tester does semi-manually by diffing responses across sessions.
A traditional DAST/WAF pass, run against a single identity with no notion of "whose object is this," will not surface it - the response looks fine in isolation.
This is also why BOLA regressions creep back in over time even in teams that "fixed" it once: a new endpoint ships, someone queries the database directly by ID without routing through the authorization layer, and the exact same class of bug reappears under a different route.
What actually works
None of the fixes here are exotic. The problem isn't that the defense is unknown - it's that it has to be applied everywhere, consistently, by default, which is a process and architecture problem more than a technology one.
Centralize the object-level check. Don't let each handler reimplement "does this user own this object" from scratch. Put it in shared middleware, a decorator, a base repository method, or a policy-decision layer every data-access path is forced through - something like Open Policy Agent/Rego for externalized policy, or an equivalent internal library. The goal is that "check ownership before returning the object" stops being a thing an engineer has to remember and becomes a thing that's structurally hard to skip.
Push what you reasonably can to the API gateway. A gateway is a good enforcement point for coarse-grained authorization - is this caller allowed to hit this route at all, does this scope/role cover this endpoint, is this request rate-bounded and logged. It is not a substitute for object-level checks, because the gateway generally doesn't know your data model either; it knows routes and scopes, not "does this specific user own this specific row." Treat the gateway as the first filter, not the only one.
Use schema validation for what it's actually good at. JSON Schema / OpenAPI validation at the edge stops malformed payloads, type confusion, and a category of injection-adjacent bugs. It will happily wave through GET /api/invoices/1041 from the wrong user, because that request is valid by every rule schema validation checks. Keep it - it earns its place - but don't count it as an authorization control, because it isn't one.
Prefer non-sequential identifiers. Swapping auto-incrementing integers for UUIDs (or otherwise unpredictable IDs) removes casual enumeration - an attacker can't just walk 1, 2, 3, ... through your ID space. OWASP's own mitigation guidance recommends this. It measurably raises the cost of a lazy attack.
Test cross-account access automatically, in CI. Maintain two (or more) test identities per resource type and assert, as part of your test suite, that account B's token against account A's object IDs returns 403/404, not 200. This is the closest thing to "automating BOLA detection" that actually works, because it encodes the one piece of context generic scanners lack: which identity should own which object.
The honest limits
None of this is a silver bullet, and pretending otherwise sets teams up to ship the same bug again.
UUIDs are security-through-obscurity, full stop. They don't fix the missing check - they raise the bar against blind guessing. An attacker who obtains one valid UUID through a leaked log line, a referral URL, a webhook payload, or a completely unrelated IDOR bug elsewhere in the same system can still walk straight through the front door if the ownership check itself was never written. Unpredictable IDs buy you defense against the laziest version of the attack, not the determined one.
Centralized authorization middleware only helps if every endpoint actually routes through it. This is the failure mode that bites mature teams specifically: the core CRUD surface is clean, the check is well-tested, and then someone adds a reporting endpoint, an internal admin tool, or a webhook handler that queries the ORM directly because "it's just an internal thing." One skipped endpoint reintroduces exactly the same class of bug the centralized check was built to eliminate. Centralization reduces the number of places the bug can hide; it does not make the bug impossible.
And ultimately, BOLA testing is domain-specific in a way that resists full automation. Knowing that account B should never see account A's invoice requires knowing that invoices belong to accounts in your particular system - not a general fact a scanner can infer, a fact specific to your schema. Generic tooling can execute the cross-account replay once you've told it what "cross-account" means for your objects; it cannot discover your ownership model on its own. Someone who understands the data model still has to define what correct authorization looks like before any tool, human or automated, can check for its absence.
That's the actual shape of the problem: not a missing feature in your stack, but a check that has to be re-asserted, correctly, on every single object-accepting endpoint you will ever ship.
Sources
OWASP API Security Top 10 (2023), API1: Broken Object Level Authorization - https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/
OWASP API Security Top 10 (2023), full table of contents - https://owasp.org/API-Security/editions/2023/en/0x00-toc/
OWASP API Security Top 10 (2023), API3: Broken Object Property Level Authorization - https://owasp.org/API-Security/editions/2023/en/0xa3-broken-object-property-level-authorization/
Wiz, "OWASP API Security Top 10 Risks and How to Mitigate Them" - https://www.wiz.io/academy/api-security/owasp-api-security
Indusface, "OWASP API1:2023 – Broken Object Level Authorization" - https://www.indusface.com/learning/owasp-api-top-10-broken-object-level-authorization/
Snyk, "BOLA: The API Vulnerability Hiding in Plain Sight" - https://snyk.io/articles/bola-the-api-vulnerability-hiding-in-plain-sight/
Akamai, "What Is BOLA?" - https://www.akamai.com/glossary/what-is-bola
Axeploit, "Hunting Business Logic Flaws: Why Traditional Scanners Miss BOLA and IDOR Vulnerabilities" - https://axeploit.com/blog/why-traditional-scanners-miss-bola-and-idor-vulnerabilities
Precursor Security, "Business logic vulnerabilities: what automated scanners miss" - https://www.precursorsecurity.com/blog/business-logic-vulnerabilities-what-scanners-miss
Salt Security, "API1:2023 – Broken Object Level Authorization" - https://salt.security/blog/api1-2023-broken-object-level-authentication
