BIPI
BIPI

The IDOR Pattern Still Wins in 2026

Cybersecurity

Insecure Direct Object Reference is the simplest authorization bug and still the most prevalent in our pentest reports. Why it persists, the three places we always find it, and the data-layer pattern that fixes it once and for all.

By Arjun Raghavan, Security & Systems Lead, BIPI · April 26, 2026 · 7 min read

#web-security#idor#pentest#authorization

Of every 100 web application pentest reports we deliver, roughly 80 include at least one Insecure Direct Object Reference (IDOR) finding. The bug is conceptually trivial — change an ID in the URL or request body, get someone else's data — but it persists because frameworks make it easy to authenticate users and hard to authorize per-resource.

The pattern that creates IDOR is also the pattern most engineers were taught to write. /api/orders/:id loads the order by primary key, returns it. The controller checks 'is this user logged in?' and stops. It does not check 'does this user own this order?' Until the answer to that second question is enforced at every code path that touches the resource, IDOR keeps winning.

The three places we always find IDOR

Place one: detail endpoints. /api/orders/12345, /api/users/789, /api/files/abc. The same code path that returns the resource for the legitimate owner also returns it for any authenticated user who guesses or scrapes the ID. Sequential integer IDs make this trivial; UUIDs slow it down but do not stop it (employees, support staff, leaked URLs).

Place two: list filters. /api/orders?customer=12345 returns orders for the customer ID in the query string. The handler trusts the query value. Change the value, get someone else's orders. This pattern is rarer in modern frameworks but appears in legacy admin tools and reporting endpoints.

Place three: write operations. POST /api/orders/12345/refund with a body that specifies amount and destination account. The handler checks login, processes the refund. Whose refund? Whichever order ID was in the URL. We have demonstrated unauthorised refunds on production payment systems this way.

Why frameworks have not fixed it

Authentication is a horizontal concern that fits cleanly into middleware. Authorization is per-resource, per-action, and depends on data the framework cannot know about until it queries the resource. Most frameworks ship strong auth (login, sessions, tokens) and weak authz (decorators, policy classes that the developer has to invoke).

Some frameworks try harder. Rails' Pundit, Django's permissions, NestJS guards, .NET's authorization policies. They all reduce to: the developer has to remember to call the policy on every endpoint. The default is no check. The forgotten endpoint is the IDOR.

The data-layer pattern that fixes it

Move the authorization filter from the controller to the data layer. Every query for resources owned by a tenant or user is automatically scoped to that tenant or user. The application code cannot accidentally bypass it because the unscoped query is not exposed.

Concretely: a single accessor method like Orders.forCurrentUser() that returns a query scoped to the authenticated user. All controllers use this accessor. There is no Orders.find(id) — there is only Orders.forCurrentUser().find(id), which returns null for orders the user does not own. Test coverage on the accessor catches the entire class of bug.

Testing for it

Two types of tests catch IDOR. Unit tests where you authenticate as user A and try to access user B's resource: the test should expect 403 or 404, never the resource. Property-based tests that fuzz IDs against the entire resource set: any access that returns data for a resource the test user does not own is a failure.

We add an IDOR-fuzzer to every pentest engagement now. It walks the OpenAPI spec or HAR file, generates ID variations, replays each request as a different test user, flags any 200 response that contains data the second user should not see. It finds something on every engagement.

Closing

IDOR is unglamorous. It does not require kernel exploits or zero-days. It is what an attacker tries first, second, and third. The fix is also unglamorous: move authorization where the data lives and stop trusting controllers to remember. The teams that ship IDOR-free code do not have smarter developers; they have a pattern that makes IDOR impossible to introduce by accident.

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.