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
- Enumerate every object-referencing endpoint. REST path parameters, query-string ids, request-body ids, and every GraphQL
node(id:)and typed resolver. This list is the attack surface; if it is incomplete, the test is incomplete. - Provision two real, low-privilege identities, User A and User B, in the same tenant and in separate tenants. Capture each one's own object ids by driving the app normally as that user.
- For every object, replay A's authenticated request carrying B's object id, and B's request carrying A's id. Same session, foreign object. A response with someone else's data is a confirmed read BOLA.
- Repeat the swap across write and action operations, not just reads. Update, delete, cancel, approve, transfer, state transitions. This is the 41.7 percent the read-only pass never touches.
- Decode and swap GraphQL Global IDs. Base64-decode B's node id, re-issue it inside A's query, and confirm the resolver re-checks ownership rather than trusting the opaque-looking value.
- Include one vertical pair. Point a regular user at an admin-only object or operation to catch the user-to-admin failures that make up 11.9 percent of the confirmed set.
- Record the evidence, not just the verdict. The two requests, the two identities, and the response that should never have been returned. That paired artifact is the proof.
How Celvex catches this
Find. Prove. Fix. Verify.
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.
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.
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.
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
- arXiv:2605.25865 (25 May 2026): a taxonomy of broken object-level authorization from more than 100 bug-bounty disclosures
- OWASP API Security Top 10, API1:2023 Broken Object Level Authorization
- MITRE CWE-639: Authorization Bypass Through User-Controlled Key
- MITRE CWE-284: Improper Access Control
- CELVEX Group: Proof Capsule format
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 →