Here's a test you can do right now. Sign in to your app and open any page that shows a single record: an invoice, an order, a project, a profile. Look at the URL. There's a good chance it contains an ID, something like /invoices/1042 or /projects/8f3a2c.
Now change it to another one. Try 1041.
If you're looking at someone else's invoice, you've found an insecure direct object reference, usually shortened to IDOR. It's the simplest version of the most common serious flaw in web applications, and apps built with AI tools are especially prone to it.
Why it's number one
OWASP, the non-profit behind the best-known list of web application risks, ranks broken access control first in its Top 10:2025. It held the same spot in the 2021 edition. Its API security list has a separate entry for this exact pattern, called broken object level authorization.
It's common because it's invisible. The page works. The feature works. Nothing errors. The app returns invoice 1041 perfectly to whoever asks. It just never checked whether the person asking should see it.
A well-known example: in 2018, security journalist Brian Krebs reported that an API behind the US Postal Service website let any logged-in user query account details for other users, including email addresses, usernames, street addresses and phone numbers. Around 60 million users were affected. The researcher who found it had reported it a year earlier. You needed an account, but any account would do.
Why AI tools produce it so often
When you ask an AI tool for "a page that shows an invoice", it builds exactly that: take the ID from the URL, fetch the invoice, render it. Checking that the invoice belongs to the signed-in user is a separate requirement, and nothing in the request mentioned it.
The model isn't being careless. It's doing what it was asked. Security requirements that depend on your business ("customers only see their own invoices, accountants see all invoices for their client") need to be stated, and founders rarely state them because they seem obvious.
The ten-minute test
You need two accounts. Call them A and B.
- Sign in as A. Create something private: an invoice, a document, a note, an order. Anything that belongs to A.
- Find its ID. It's often in the URL. If not, open the browser's developer tools (F12), go to the Network tab, reload, and look at the requests your app makes. The ID will be in one of them.
- Sign out and sign in as B.
- Paste A's URL into the address bar. Does A's record appear?
- Now try the underlying request. In the Network tab, find the request that loaded one of B's own records, right-click it, and choose "Copy as fetch" or "Copy as cURL". Ask your AI tool to swap B's record ID for A's and run it. Does A's data come back?
- Try the same for editing and deleting, if your app has those actions. These are often less protected than viewing.
If any step shows or changes A's data while you're signed in as B, you have an IDOR.
Also try step 5 with no one signed in at all. Some endpoints skip the login check entirely.
The fix
The fix is always the same principle: every request that touches a specific record must check, on the server, that the signed-in user is allowed to touch it.
In a traditional backend, that's a line in each route: fetch the invoice where the ID matches and the owner is the current user. In a Supabase app, it's a Row Level Security policy that ties rows to auth.uid(), which our plain-English RLS guide walks through.
You can hand this to your AI tool as a prompt:
For every API route and database query that reads, updates or deletes a single record by ID, add a server-side check that the signed-in user owns that record or has an explicit role that allows access. Return 404 if not. List every route you changed.
Then run the two-account test again. Don't take the tool's word that it's done.
Things people think fix it, but don't
Hiding the ID from the URL. The ID is still in the network requests, where anyone can see it.
Random IDs instead of numbers. They make guessing harder, which is worth having, but IDs leak through shared links, emails and API responses. A check on the server is still needed.
Checking in the frontend. If the page hides A's invoice from B but the API still returns it, the API is what an attacker talks to.
This test is item two in our pre-launch security checklist, right after checking your database rules. Between them they cover most of the serious findings in AI-built apps.