Change 1043 to 1044 in the address bar and press enter. If you get somebody else's invoice back, with a completely normal 200 OK, that is an IDOR, and it is the single most common serious bug we find in apps built with AI coding tools.
What makes it hard to spot in a code review is that there's nothing wrong on the screen. The code does exactly what it says. The problem is a line that was never written.

Reading the diagram
The top box, in red. A request arrives for invoice 1043. The red says what red always says in these diagrams: anyone can send this. Nothing stops a signed-in customer typing a different number, and nothing should, because that's just a URL.
The grey box. Here's the query your AI assistant wrote. WHERE id = 1043. Read it on its own and it's correct: you asked for the invoice with that id, and that's the invoice with that id.
The solid red arrow, going left. This is what actually happens when you deploy. The row comes back, your API serializes it, the customer sees an invoice that isn't theirs. Status code 200. No error in your logs, no alert, nothing to notice. It looks exactly like a successful request because it is one.
The dotted arrow, going right. This is the whole diagram. That branch is not in your codebase. It's drawn to show the shape of an absence, which is the thing prose is worst at describing and the reason this diagram exists at all. The amber box holds one extra condition: AND owner_id = your session user.
The two green boxes. With the condition present, the query returns nothing for a row you don't own, and your handler answers 404. Notice that it's the same 404 a genuinely nonexistent invoice would get. That's deliberate. A 403 would confirm the record exists, which hands an attacker a working directory of your customer ids.
The session user is the part people get wrong. The condition has to compare against the user id from the server-side session, never against a user id that also came in with the request. An owner_id read from a hidden form field, a query parameter, or a JWT claim your app never verified is attacker-controlled too, and the check becomes theatre.
Why this one shape covers so many bugs
Swap the nouns and the diagram is unchanged. A document, a team, a project, a support ticket, a webhook endpoint, a file in a bucket. Anywhere your app looks something up by an identifier that came from the request, this same branch is either present or missing.
That's also why it's worth checking systematically rather than page by page. In scans we see apps where the ownership check is present on four routes and absent on the fifth, usually the one added last, after the pattern had stopped being top of mind.
The source
---
title: "IDOR is one missing condition"
---
flowchart TB
REQ["GET /api/invoices/1043<br/>change 1043 to 1044 and press enter"]
Q["The query the AI wrote<br/>WHERE id = 1043"]
LEAK["Another customer's invoice<br/>returned with a 200 OK"]
GATE["The condition that is missing<br/><b>AND owner_id = your session user</b>"]
SAFE["404 Not Found<br/>same as a row that never existed"]
OWN["Your own invoice"]
REQ -->|"id read straight from the URL"| Q
Q -->|"what your code actually does"| LEAK
Q -. "what it never does" .-> GATE
GATE -->|"row is not yours"| SAFE
GATE -->|"row is yours"| OWN
classDef public fill:#fef2f2,stroke:#ef4444,stroke-width:2px,color:#1c1917
classDef gate fill:#fffbeb,stroke:#f59e0b,stroke-width:2px,color:#1c1917
classDef private fill:#ecfdf5,stroke:#10b981,stroke-width:2px,color:#1c1917
classDef store fill:#f5f5f4,stroke:#57534e,stroke-width:2px,color:#1c1917
class REQ,LEAK public
class GATE gate
class SAFE,OWN private
class Q store
linkStyle 1 stroke:#ef4444,stroke-width:3px
What is an IDOR vulnerability?
Insecure Direct Object Reference. Your app takes an identifier from the request, usually straight out of the URL, looks up that record, and returns it without ever asking whether the record belongs to the person asking. Change 1043 to 1044 in the address bar and you get someone else's data with a normal 200 OK.
Why do AI coding tools produce IDOR bugs so reliably?
Because you asked for a feature, and authorization isn't a feature. A prompt like "let users view their invoices" describes the happy path, so the model writes the happy path: find the row, send it back. Nobody wrote down the part where the app refuses. The generated code isn't broken, it's incomplete in a way that runs perfectly in testing.
How do I test my own app for IDOR in one minute?
Sign in as one user, open any page whose URL contains a numeric id, and change the number by one. If you see another account's data, you have an IDOR. Do it again with a second account so you're certain you aren't just looking at your own second record.
Should the app return 404 or 403 when the row belongs to someone else?
404, in nearly every case. A 403 confirms the record exists, which tells an attacker enumerating ids exactly which ones are real. Answering 404 for both "no such row" and "not your row" gives away nothing.
Do random UUIDs fix IDOR?
No. They make guessing harder, not impossible, and they do nothing once an id leaks through a shared link, a referrer header, an export, or a support ticket. Unguessable identifiers are a speed bump. The ownership condition is the fix.
Is the Condition There on Every Route?
CheckYourVibe probes your deployed API with ids that belong to somebody else and reports the routes that answer with data.