Triaging IDOR and BOLA Reports: Verifying Authorization Actually Fails
How to triage IDOR/BOLA reports: proving authorization really fails, enumerability testing, cross-tenant vs cross-user impact, and why severity hinges on data.
IDOR (or BOLA, if you prefer the OWASP API name, Broken Object Level Authorization) is deceptively easy to report and surprisingly easy to misjudge. The report says "I changed the ID and got someone else's data." Sometimes that's a critical multi-tenant breach. Sometimes the object was public by design and the researcher discovered that account IDs are sequential, which is not a vulnerability. The gap between those two outcomes is the entire triage job.
Unlike XSS, an IDOR rarely announces itself with a payload firing. There's no alert(1) moment. You have to reason about intent: was that object supposed to be reachable by this identity? That question doesn't have a client-side answer, which is why lazy IDOR triage is so error-prone in both directions.
First question: does authorization actually fail?
The reporter incremented an ID and saw a response with data in it. Before anything else, establish that the data belongs to someone else and that the accessing account was not authorized to see it. Three failure modes to rule out:
- The object is intentionally public. Public profiles, published blog posts, shared documents with link access, product catalogue entries. Enumerating public objects is not IDOR. It's reading public data in a slightly gauche way. If an unauthenticated
GET /users/123/profilereturns the same fields the app shows on a public profile page, there's no authorization to break. - The accessing account is actually authorized. Team members, org admins, support roles, and delegated-access relationships can all legitimately reach objects that "belong" to another user ID. A report from an account that shares a tenant, team, or explicit grant with the target isn't demonstrating a broken control. It's demonstrating the control working.
- The data is the reporter's own. More common than you'd think: the researcher changed the ID, got a
200, and didn't notice the response echoed their own record, or a generic empty template, rather than the victim's data.
The clean test is a two-account reproduction. Account A and Account B, no relationship between them, no shared tenant. Authenticate as A, request B's object using A's credentials, and confirm the response contains B's data, specifically B's data, verified against what you see logged in as B. One account changing IDs and eyeballing a 200 proves nothing; the 200 might be a template, an error page with status 200, or the attacker's own record.
GET /api/v2/invoices/8814 HTTP/1.1
Host: api.example.com
Authorization: Bearer <account-A-token>
If invoice 8814 belongs to Account B's org and this returns B's line items (verified side by side with B's own view) with A's token, authorization failed. If it returns 403, 404, or A's own invoice, it didn't.
Enumerability: how bad is the blast radius?
A single cross-user read is a bug. A single cross-user read where the object identifier is a sequential integer is a bulk data exposure, because the attacker doesn't need to know victim IDs: they count.
So test the identifier:
- Sequential integers (
8814,8815,8816) → trivially enumerable → the whole table is reachable. This is the severity multiplier that turns a one-record IDOR into a breach. - UUIDv4 / random 128-bit → not practically enumerable → the attacker needs to obtain valid IDs some other way (leaked in a referrer, another endpoint that lists them, a shared link). Still a valid IDOR, but the impact is gated by how the attacker learns IDs.
- Predictable-but-not-sequential (timestamps, short hashes, base64'd integers) → decode before you conclude. A "random-looking" ID that's a base64-encoded row number is fully enumerable, and a researcher who demonstrates the decode has shown real impact.
Enumerability doesn't decide whether it's a bug (non-enumerable IDORs are still bugs), but it decides how much of the dataset is exposed, which is most of the severity conversation. When you write the verdict, name it explicitly: "sequential integer ID, full invoice table enumerable" is a different sentence from "UUID, requires known object reference."
Cross-tenant vs cross-user vs cross-role
Not all "other people's data" is equal. The impact ceiling depends on what boundary broke:
- Cross-user, same tenant: one customer's user reaches another user's object inside the same organisation. Real, but often bounded by what a single account can hold.
- Cross-tenant: a user in Org A reaches Org B's data. This is the one that ends up in breach notifications. In a B2B/SaaS product, cross-tenant IDOR is close to the worst outcome the object-authorization layer can produce, because it violates the core promise the product sells.
- Cross-role / privilege boundary: a standard user reaches an admin-only object or action. If the object is a settings toggle, an audit log, or a user-management action, you're near horizontal-plus-vertical territory.
A report that says "IDOR" without telling you which boundary broke is under-specified. Part of triage is establishing it, because "I read another user in my own test org" and "I read a different customer entirely" get very different scores.
Read vs write
The verb matters as much as the object. A read IDOR leaks data. A write IDOR changes it, and the same broken authorization check that lets you GET /invoices/8814 might let you PUT or DELETE it.
Always test the write path if the read path is broken, because they frequently share the same missing check:
- Read → confidentiality impact, scaled by data sensitivity and enumerability.
- Write / update → integrity impact; attacker modifies victim records. Often higher severity than the read.
- Delete → availability plus integrity; attacker destroys victim data.
- Action / state-change (transfer funds, change email, revoke access) → this is where an IDOR becomes account takeover or worse.
A researcher who reports the read and stops has under-explored the finding. You don't have to do their work, but noting "read confirmed; write path also missing the tenant check → attacker can modify victim invoices" is the difference between a medium and a critical, and it's the kind of delta worth checking before you finalise severity.
Severity hinges on data sensitivity and scale
Two IDORs with identical mechanics can be an Informative and a Critical, entirely because of what the object holds and how many there are:
| Object exposed | Enumerable? | Rough impact |
|---|---|---|
| Public display name | n/a (already public) | None / Informative |
| Order history, addresses, partial PII | Sequential ID | High (bulk PII exposure) |
| Full PII, payment data, government IDs | Sequential ID | Critical |
| Another tenant's business data | Any known ref | High to Critical (cross-tenant) |
| Non-sensitive metadata, single record | Random UUID | Low |
This is why IDOR severity can't be assigned from the vulnerability class alone. "IDOR" is not a severity; "enumerable cross-tenant read of full customer PII" is. The class tells you the mechanism; the object and the scale tell you the number. Our severity guide treats data sensitivity and scale as first-class inputs precisely because bugs like this collapse without them.
Watch the dedup trap
IDOR is the classic case where different endpoints are the same bug. A missing tenant scope in shared middleware surfaces as "IDOR on /invoices," "IDOR on /projects," and "IDOR on /webhooks": three reports, one broken line of code, one fix. Rate the root cause, not the URL count, or you'll pay three bounties for one bug. That's covered in depth in our dedup deep dive; for IDOR specifically, always ask whether the authorization check that failed is per-endpoint or shared infrastructure.
The IDOR triage checklist
- Two unrelated accounts; confirm A reads/writes B's object with A's credentials, verified against B's actual data.
- Rule out: object intentionally public, accessor legitimately authorized, response is the attacker's own record.
- Classify the identifier (enumerable or not) and decode anything that looks random before concluding.
- Establish the boundary: cross-user, cross-tenant, or cross-role.
- Test read vs write vs action; the missing check often covers all three.
- Rate on data sensitivity × scale, not on the word "IDOR."
- Check whether it's shared-infrastructure root cause before treating parallel reports as separate findings.
IDOR rewards patience: the difference between "public by design" and "cross-tenant breach" is a two-account test most rushed queues skip. TRIAGERS™ fields on-demand triage teams who run that test every time and rate on impact, not vocabulary. If your authorization findings deserve a closer look, let's talk.