All posts
VulnerabilitiesIDOR

IDOR: The Vulnerability That Lets Anyone Access Your Users' Data

Insecure Direct Object Reference (IDOR) is the most common critical vulnerability in web apps — and AI-generated code is especially prone to it. Here's a complete guide to finding and fixing IDOR.

February 17, 20268 min read

Imagine you're browsing your order history at /orders/1234. You change the URL to /orders/1235. If you can see someone else's order, that's an IDOR vulnerability. Insecure Direct Object Reference (IDOR) is one of the simplest vulnerabilities to understand, one of the most common vulnerabilities to find, and one of the most expensive vulnerabilities to have in your application.

What Is IDOR?

Insecure Direct Object Reference occurs when an application uses a user-controllable identifier to directly access an object (a database record, file, or other resource) without first verifying that the current user has permission to access that specific object.

The identifier can be anything: a numeric database ID, a UUID, a username, a filename, an email address. What makes it an IDOR vulnerability isn't the type of identifier — it's the missing authorization check. The application trusts the identifier without asking: does this user have permission to access the thing this identifier points to?

Real-World IDOR Examples

Example 1: API endpoint with numeric ID

GET /api/invoices/5827 returns invoice data. The server checks that the request includes a valid session token, but doesn't check whether the invoice belongs to the authenticated user. Any logged-in user can read any other user's invoice by enumerating IDs.

Example 2: Profile update endpoint

PUT /api/users/profile with body {"userId": "42", "email": "[email protected]"} updates user 42's email. The server uses the userId from the request body rather than from the authenticated session — any user can update any other user's email.

Example 3: File download

GET /files/download?filename=report_user42.pdf serves the file from storage. The server doesn't check whether the authenticated user is user42. Any user who guesses or enumerates the filename can download other users' files.

Notable breaches

IDOR vulnerabilities have been behind some of the largest data breaches in history. In 2020, a major US health insurer exposed 1.9 million patient records due to an API IDOR flaw. Facebook, Instagram, and dozens of Fortune 500 companies have paid bug bounties in the tens of thousands of dollars for IDOR reports.

Why AI-Generated Code Is Especially Prone to IDOR

AI coding assistants are excellent at generating functional code. When you ask Cursor or Copilot to write an API endpoint that retrieves an order by ID, you get working code that queries the database correctly, handles errors, and returns the right data. What you often don't get is the authorization check.

This happens for a few reasons. AI models are trained to produce code that satisfies the functional requirement in the prompt. Security requirements are often implicit — the developer assumes the AI will add them, and the AI assumes the developer will add them. The result: the endpoint works, passes testing, and ships with no ownership check.

How to Find IDOR in Your Own Code

Audit your API endpoints and ask these questions for each one that accepts an ID parameter:

  1. Is the ID user-controllable? (URL parameter, query string, request body, cookie)
  2. Does the endpoint query the database using this ID?
  3. Before serving the response, does the code verify that the queried resource belongs to the authenticated user?

If the answer to #3 is 'no' or 'I'm not sure', you likely have an IDOR. Common code patterns that indicate missing ownership checks:

  • db.query('SELECT * FROM orders WHERE id = ?', [req.params.id]) with no WHERE user_id = ? clause
  • const resource = await Resource.findById(req.params.id) with no subsequent owner check
  • Using the ID from req.body or req.query to update a record without confirming the record belongs to req.user.id

How to Fix IDOR Vulnerabilities

The fix is always the same: add an ownership check to the query. Instead of fetching a resource by ID and then checking the owner, fetch the resource by both ID and owner simultaneously. If no record is found, return 404 (not 403 — returning 403 confirms the record exists, which is information leakage).

Vulnerable pattern

const invoice = await db.invoice.findUnique({ where: { id: invoiceId } }); if (invoice.userId !== session.userId) return 403;

Fixed pattern

const invoice = await db.invoice.findUnique({ where: { id: invoiceId, userId: session.userId } }); if (!invoice) return 404;

The second approach is not just more secure — it's also a single database round-trip instead of two. It returns 404 for both 'record doesn't exist' and 'record exists but you don't own it', preventing enumeration of other users' IDs.

Preventing IDOR at Scale

For applications with many API endpoints, auditing each one manually is error-prone. Some approaches that help at scale:

  • Row-level security in your database (PostgreSQL RLS, Supabase RLS): enforce ownership at the database layer so queries automatically filter to the current user's data regardless of what the application code passes.
  • Middleware-based authorization: create a wrapper function that enforces ownership checks and use it consistently across all resource-fetching endpoints.
  • Integration tests that attempt cross-user access: in your test suite, create two users, create a resource as user A, and verify that user B gets 404 when trying to access it.
  • Automated penetration testing: tools that specifically probe for IDOR will catch what code review and static analysis miss.

Ready to check your app?

Find your vulnerabilities before attackers do.

Pentrust runs AI agents that chain real exploits against your application and provides copy-paste fixes for every finding. Full pentest in under 30 minutes.

Run a free pentest