IDOR Vulnerabilities: The Most Common Serious Bug in SaaS
If you look at bug bounty reports for SaaS companies, one class of bug appears again and again: insecure direct object references, or IDOR. They are simple, easy to miss in code review, invisible to most scanners, and often expose customer data directly.
What an IDOR looks like
An application exposes a reference to an object, such as an ID in a URL or request body, and does not check that the current user is allowed to access it.
GET /api/invoices/48213
The user changes the ID:
GET /api/invoices/48214
And receives another customer's invoice. That is an IDOR.
Variations to know
- Read IDOR: viewing another user's data.
- Write IDOR: updating or deleting another user's objects with
PUT,PATCHorDELETE. - IDOR in request bodies:
{"user_id": 512, "email": "[email protected]"}changes someone else's account email. - Nested resources:
/orgs/12/projects/99checks access to org 12 but not that project 99 belongs to org 12. - Indirect IDOR: file downloads, exports or reports keyed by guessable IDs.
- Cross-tenant IDOR: the most severe form in B2B SaaS, exposing one company's data to another.
Why random IDs are not a fix
Switching from sequential IDs to UUIDs makes guessing harder, but IDs leak constantly: in URLs shared by users, in API responses, in logs, emails and analytics. Obscurity reduces risk slightly; authorisation removes it.
Why IDORs are so common
- Frameworks make it easy to load an object by ID and hard to remember to check ownership.
- The UI only shows users their own objects, so the bug is invisible in normal use.
- APIs grow quickly, with each endpoint implementing its own checks.
- AI-generated code frequently fetches by ID without any authorisation logic.
How to prevent IDOR systematically
1. Scope every query to the current user or tenant.
// Vulnerable
const invoice = await db.invoice.findUnique({ where: { id } });
// Safe: ownership is part of the query
const invoice = await db.invoice.findFirst({ where: { id, orgId: session.orgId } });
if (!invoice) return notFound();
2. Centralise authorisation in a policy layer or middleware, not scattered across handlers.
3. Use row-level security in the database where supported, such as Postgres RLS, as a second layer.
4. Return 404 rather than 403 for objects the user cannot access, to avoid confirming they exist.
5. Write tests with two users. For every endpoint, assert that user B cannot read or modify user A's objects.
Testing for IDOR
Finding IDORs requires at least two accounts, ideally in different tenants, and testing every endpoint with the other account's object IDs. This is tedious manually and impossible for traditional scanners, which do not understand ownership.
SecurityInfinity agents test authenticated endpoints across users and tenants, verify unauthorised access, and give the specific authorisation fix. Start free.
Frequently asked questions
How severe is an IDOR?
It depends on the data and action. Reading another customer's invoices or changing their account details is typically high or critical.
Are IDOR and BOLA the same?
Broken Object Level Authorisation (BOLA), the top item in the OWASP API Security Top 10, is essentially IDOR in APIs.
Can a WAF prevent IDOR?
No. The requests look completely legitimate. Only server-side authorisation checks prevent IDOR.