← Back to Attack Research

The BOLA taxonomy: API risk number one, and 41.7% of it is state-changing

Broken Object Level Authorization is still the top API risk, and the numbers say most of it is worse than a data leak. A taxonomy of 107 classified bug-bounty disclosures finds that of the confirmed cases, 41.7 percent are state-changing actions on another user's object, not simple reads. Here is the evidence, the shape of the family, and the two-identity harness that finds it.

Broken Object Level Authorization has held the number one spot on the OWASP API Security Top 10 for years, and a fresh empirical taxonomy explains why it stays there: it is not one bug, it is a family, and the severe members are not the ones most testing looks for. Reviewing 107 fully classified bug-bounty disclosures, the study confirmed 84 of them, or 78.5 percent, as in-scope BOLA. Of those confirmed cases, 41.7 percent were not object reads at all but unauthorized state-changing actions on another user's object, and GraphQL Global IDs were exploited systematically across major platforms. This is CWE-639 (Authorization Bypass Through User-Controlled Key) and CWE-284 (Improper Access Control), and the read-only IDOR most scanners chase is the smaller half of it.

This piece is about a class, not a product. The empirical grounding is a public arXiv preprint, arXiv:2605.25865 (25 May 2026), which builds a taxonomy of broken object-level authorization from more than one hundred real bug-bounty disclosures. We use its classification because it turns a familiar warning into measured structure, and because that structure changes how you should test. We do not republish any target's exploit detail. We explain what the taxonomy found, why the dangerous cases cluster where they do, and how to build a harness that catches them on your own API. Verifiable security.

The class in one paragraph

BOLA is missing per-object authorization. An API endpoint takes an object identifier from the request, a numeric row id in a REST path, a value in a query string, a node id in a GraphQL query, and returns or modifies that object. The endpoint authenticates the caller correctly, so the request carries a valid session or token. What it never does is the second check: does this authenticated caller actually own, or have the right to touch, the specific object this id points to. When that check is absent, any logged-in user can substitute someone else's object id and operate on data that is not theirs. The word broken in the name is precise. Authentication is intact. It is authorization, at the granularity of the individual object, that is broken. That is why BOLA hides so well: every request is from a real, logged-in user, so nothing looks anomalous at the session layer.

What the taxonomy actually found

The value of the study is that it stops treating BOLA as a single undifferentiated bug and sorts the disclosures into categories. Three findings matter for how you test.

First, the base rate is high and real. Of 107 reports that could be fully classified, 84 were confirmed as in-scope BOLA, a 78.5 percent confirmation rate. This is not a class inflated by mislabeled reports. When a researcher calls something BOLA, they are usually right, and the category is doing real work.

Second, and this is the headline, the severe cases are state-changing. The taxonomy's largest confirmed category is what it calls Action-Level Object BOLA, unauthorized state-changing actions on another user's object, and it accounts for 41.7 percent of confirmed cases. This is the difference between reading another user's invoice and cancelling it, between viewing another tenant's record and deleting it. Testing that only replays read requests, the classic enumerate-the-id IDOR check, misses the plurality of confirmed BOLA outright.

Third, the failure is not always horizontal. 11.9 percent of confirmed cases were vertical, a regular user reaching an object or operation reserved for an administrator, which is a privilege escalation wearing a BOLA costume. And running underneath all of it, GraphQL Global IDs were systematically exploited across major platforms, because a base64-wrapped node id feels opaque and is treated as if opacity were authorization. It is not. Decoding a Global ID is trivial, and if the resolver behind node(id:) does not re-check ownership, the graph is a BOLA buffet.

Object-read IDOR is the part everyone tests for. State-changing actions and GraphQL node ids are where the confirmed severe cases actually cluster.

Why state-changing actions are the severe cluster

A read-only BOLA is a confidentiality failure: an attacker sees data that is not theirs. Serious, but bounded. An action-level BOLA is an integrity and availability failure on top of the confidentiality one, because the attacker does not just observe another user's object, they change its state. They mark a foreign order paid, reset another account's configuration, transfer a resource out of a tenant, delete a record that anchors someone else's workflow. The blast radius is larger because the operation writes, and writes are hard to walk back. This is also why the category is under-tested. Enumerating ids to read is safe and common in test suites. Exercising the write and action paths against another identity's objects is the part teams skip, because it feels risky to run and awkward to build. The taxonomy says that is exactly the part where the confirmed severe findings live.

How to test for it: the two-identity harness

BOLA cannot be found by scanning one user's traffic, because from one identity every object id the app hands you is legitimately yours. You need two identities and a disciplined swap. The harness below is the operational core, and it is what a real test crawls and replays rather than eyeballs.

Two-identity BOLA test harness

How Celvex catches this

Find. Prove. Fix. Verify.

Find

We enumerate every object-referencing endpoint across REST path and query ids and GraphQL node ids, then drive the two-identity harness so each object is exercised as its owner and as a stranger, across reads and state-changing actions alike.

Prove

A successful cross-identity request is a confirmed BOLA. It becomes an Ed25519-signed Proof Capsule carrying both identities, the swapped request, and the response that leaked or mutated a foreign object, reproducible offline by you or your auditor.

Fix

Enforce object-level authorization at the data layer: an ownership or tenant check on every fetch and every mutation. Use unpredictable, opaque ids as defense in depth, and decode GraphQL Global IDs through the same authorization, never around it.

Verify

We re-run the harness until every cross-identity request returns 403 or 404, then leave a regression test per object type so a future refactor cannot quietly reopen the gap.

The fix is unglamorous and that is the point. There is no clever filter, no id-obfuscation trick that substitutes for the missing check. The authorization has to live at the data layer, next to the fetch and next to the mutation, so that no route into the object, REST, query string, or GraphQL resolver, can reach it without asking the same question: is this caller allowed to touch this specific object. Opaque ids help, because guessing gets harder, but they are defense in depth, not authorization. The taxonomy's lesson is that you have to apply that check on the write paths and the graph resolvers with the same rigor you apply to the obvious read endpoints, because that is where the confirmed severe cases are hiding.

Verifiable security. Find it. Prove it with two identities and a signed capsule. Fix it at the data layer. Verify the fix held with a regression test. That is what we ship.

Sources

Does your API check who owns the object, or just who is logged in?

Free Exposure Check, no signup required. We enumerate your object-referencing endpoints, run the two-identity harness across reads and state-changing actions, and ship a signed Proof Capsule for the highest-confidence BOLA we can confirm.

Run a Free Scan →