An Insecure Direct Object Reference (IDOR) is an access-control vulnerability where an application exposes a reference to an internal object — a database row ID, a filename, an account number — and then trusts the client to only request objects it owns. When the server fails to verify that the authenticated user is actually authorized for the object being requested, an attacker simply changes the identifier (/invoice/1043 becomes /invoice/1044) and reads or modifies someone else's data. IDOR is the most common concrete form of what OWASP now calls Broken Object Level Authorization (BOLA), ranked API1:2023 at the top of the OWASP API Security Top 10.
The reason IDOR is so dangerous is that it is trivial to exploit and hard to spot in automated scans that only look for injection or memory bugs. In 2019, First American Financial Corporation exposed roughly 885 million records — mortgage documents, bank statements, Social Security numbers — because document URLs used sequential numeric IDs with no authorization check. Anyone with one valid link could enumerate every other document by decrementing the number.
How IDOR Works
The vulnerability has two ingredients that must appear together. First, the object reference is guessable or enumerable: an auto-incrementing integer, a predictable filename, an email address, or a short numeric token. Second, the server performs authentication but not authorization — it confirms who you are but never checks whether you may touch this specific object.
A typical flow looks like this. A user logs in and loads their profile at GET /api/users/500/orders. The frontend only ever sends the user's own ID, so developers assume the ID is safe. But the server-side handler loads orders purely by the ID in the path. An attacker changes 500 to 501 and receives another customer's order history. There is no exploit payload, no special tooling — just a modified request, which is why IDOR is a favorite in bug-bounty programs and a frequent root cause of large API breaches.
IDOR is not limited to reads. The same missing check applies to PUT, PATCH, and DELETE. A request that updates your shipping address can update anyone's if the handler trusts the ID in the body. It also hides in indirect places: file download endpoints, PDF export links, password-reset tokens, and GraphQL node IDs.
Vulnerable vs. Fixed
The bug is almost always a query that filters by object ID alone instead of by object ID and owner.
// VULNERABLE: loads any invoice by ID, trusts the client
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await db.invoices.findById(req.params.id);
res.json(invoice); // no ownership check — classic IDOR
});
// FIXED: scope every query to the authenticated principal
app.get("/api/invoices/:id", requireAuth, async (req, res) => {
const invoice = await db.invoices.findOne({
_id: req.params.id,
ownerId: req.user.id, // authorization is part of the query
});
if (!invoice) return res.status(404).end(); // do not leak existence
res.json(invoice);
});
Two subtle points matter. The fixed version makes the ownership constraint part of the lookup, so an unauthorized ID returns nothing. And it responds with 404 rather than 403, so an attacker cannot distinguish "does not exist" from "exists but not yours" and enumerate valid IDs.
Prevention Checklist
- Enforce authorization on every object access, server-side, in the data layer — never rely on the UI hiding a link.
- Scope queries to the current principal (
WHERE owner_id = :currentUser) rather than filtering after fetch. - Prefer unpredictable identifiers like UUIDv4 or ULIDs over sequential integers, as defense in depth (not a substitute for authorization).
- Centralize access decisions in a policy layer or middleware so no endpoint can forget the check.
- Return
404for unauthorized objects to prevent enumeration and existence leaks. - Write authorization tests that assert user A cannot read or mutate user B's objects for every resource type.
- Log and rate-limit sequential-ID access patterns to catch enumeration in progress.
How Safeguard Detects IDOR
IDOR is a runtime authorization flaw, so static rules alone miss it — you have to exercise the live application with more than one identity. Safeguard's dynamic application security testing engine crawls authenticated routes, then replays requests while swapping object identifiers and session contexts to prove whether user A can reach user B's resources. Because that produces evidence rather than a guess, findings arrive with the exact request pair that demonstrated the broken check.
For teams that want the reasoning explained in plain language, Griffin AI summarizes each finding, maps it to OWASP API1:2023 (BOLA), and drafts the ownership-scoping fix as a pull request through automated remediation. You can run the same authorization checks in continuous integration with the Safeguard CLI so a regression fails the build instead of shipping. If you are weighing coverage against a SAST-first tool, our comparison with Checkmarx walks through why access-control bugs need runtime testing to catch reliably.
Frequently Asked Questions
Is IDOR the same as BOLA? Effectively yes. Broken Object Level Authorization (BOLA) is the term OWASP uses in its API Security Top 10, and IDOR is the classic name for the same class of flaw. BOLA is the broader concept; IDOR usually refers to the concrete case of a manipulable direct reference like an ID in the URL.
Do UUIDs prevent IDOR? No — they only make objects harder to guess. A UUID that leaks through a referrer header, a shared link, or a related API response is just as exploitable if the server never checks ownership. Unpredictable IDs are useful defense in depth, but authorization on every access is the actual fix.
Why do scanners miss IDOR so often? Because there is no malicious payload to fingerprint. The request is perfectly well-formed; only the context — this user should not see this object — makes it an attack. Detecting it requires testing the same endpoint with multiple authenticated identities and comparing what each can reach.
Ready to find broken object-level authorization in your own APIs before an attacker does? Start free at app.safeguard.sh or read the setup guide in the Safeguard docs.