There is a login screen. It asks for a password. Nothing displays unless you log in.
And still, changing one digit in a URL reveals someone else's data — the most common vulnerability in apps built with AI.
What's more, you can never notice it while using your own account. It works flawlessly.
1. Authentication and authorization — two different jobs
Let's sort out the words first. These two being conflated is the root of the problem.
Authentication = establishing who you are. That's the login screen. An email address and password establish "this really is that person."
Authorization = establishing whether you may do this. May the logged-in user A view order number 124, which belongs to B? That is a separate decision.
By analogy, authentication is showing your badge to enter the building; authorization is which rooms you may enter once inside. Getting through the entrance doesn't mean you may walk into the CEO's office.
What AI omits is almost always authorization.
The reason is simple: authentication is a visible feature that gets built when you ask for "a login feature." Authorization renders nothing on screen. It looks identical whether it is implemented correctly or not at all.
2. What actually happens — a concrete case
Say your app has an order detail screen.
https://your-app.com/orders/123
123 is your order number. Now change it to 124.
https://your-app.com/orders/124
If someone else's order appears, that is the vulnerability.
OWASP — the standard body for web security — classifies this as API1:2023 Broken Object Level Authorization (BOLA) and ranks it first among API risks. First. The most common, and the highest impact.
Why "no visible link" doesn't protect you
"I don't render links to other people's orders, so it's fine" — the most common misconception.
Even with no link rendered, typing the URL sends the request. Open the developer tools and the shape of your app's requests is visible too, so constructing the URL is something anyone can do.
"Not on screen" does not imply "not reachable." The UI is decoration, not a lock.
3. Why AI omits it — the list/detail asymmetry
Here's the interesting part: AI filters the list view correctly.
Asked to "show a list of the logged-in user's orders," AI genuinely applies the "of the logged-in user" condition. So the list contains only your own orders.
But asked to "show the order detail," it tends to write code that just looks up the number.
list: "fetch all the orders belonging to the logged-in person" → correctly scoped
detail: "fetch order number 123" → never checks whose it is
The detail view has no "of the logged-in user" condition attached. Nobody asked for one.
And — because the list is correctly filtered, normal testing never reveals it. As long as you open the list with your own account and click through to a detail, you only ever see your own data.
That asymmetry is why this vulnerability is mass-produced in AI-built apps.
The more dangerous variants
The same problem occurs not just on reads but on updates and deletes.
- Deleting someone else's post
- Rewriting someone else's profile
- Cancelling someone else's order
The impact is larger than a read, and these get checked less often.
4. How to check for yourself — a browser is enough
You can confirm this without reading a line of code. Do it once before launch, without exception.
The procedure
- Create two test accounts (A and B)
- Log in as A and create some data — a post, an order, a note, anything
- Open that record's detail screen and note the URL. It will contain a number or an ID
- Log out and log in as B
- Type the noted URL straight into the address bar
The verdict
- A's data appeared → vulnerability. Go to the fix instruction in this article
- Error, 404, or "you don't have permission" → working correctly
Going further: try updates and deletes
Reads being protected doesn't mean updates are. While logged in as B, also try the edit screen for A's data — a URL like /orders/123/edit.
For one more step, try the same URL while logged out. Being redirected to the login screen is correct. If the data appears, even authentication isn't working.
5. What the correct implementation is
It isn't complicated. When you fetch the data, always include "does this belong to the logged-in user" as a condition. That's all.
[dangerous] look up by number alone
"fetch order number 123"
[correct] look up by number + owner
"fetch order number 123 that is also owned by the currently logged-in person"
In the correct form, specifying someone else's order number returns no match. "Not found" and "not permitted" produce the same outcome, so the existence of the number can't be inferred either.
An important principle: hiding is not defending
Hiding a button in the frontend, removing an item from a menu — these are usability touches, not defences.
The decision must happen on the server. Browser-side code can be rewritten freely by the person using it.
The relationship with RLS — make it two layers
If RLS is configured correctly, the same defence applies at the database layer too. So is the server-side check unnecessary?
It is not. They cover different ground.
- RLS = the database layer. Applies no matter which route the query arrives through
- Server-side check = the application layer. Can express business rules
Administrative operations that use the service_role key, for instance, bypass RLS entirely — and there only the server-side check protects you. Conversely, if AI adds a new API and forgets the check, RLS stops it.
Having both is the correct state. The design is that when one is missing, the other catches it.
6. Admin features — one more layer of trap
Admin screens (all users, delete, refund) need separate care.
Common mistakes
Only hiding the admin link in the menu. Anyone who knows the URL walks in.
Deciding "is this an admin" in the browser only. Rewrite the state from the developer tools and you're through.
Storing the role somewhere the user can edit. If the is_admin column on the users table can be updated by that user under your RLS policy, they can make themselves an administrator.
The correct shape
- Decide admin status on the server
- Verify permissions at every entry point of every admin API (one gap and it's meaningless)
- Keep the role information somewhere the user cannot rewrite — a database column with write-denied RLS, or a custom claim in the ID token
7. The instruction to paste into your AI tool
To hand both the checking and the fixing to an AI:
Find every handler in this app that takes an ID and returns, updates or deletes
data (Route Handlers, Server Actions, APIs, page data fetching), check each one
against the following, and report as a table.
(1) Whether the server verifies that the record belongs to the logged-in user
(2) If it does not, what happens when someone else's ID is supplied
(3) Which places merely hide things in the frontend
Then add an ownership check everywhere it is missing. Put the owner condition
into the data-fetching query itself rather than filtering afterwards in
application code. Cover updates and deletes, not just reads.
Also list every admin-only feature and confirm each performs its permission
check on the server. Check whether the value that determines the role can be
rewritten by the user.
I cannot read code, so explain in one plain sentence per finding what was
possible before the fix.
8. After fixing, check again
After the fix, redo the procedure in section 4.
The AI will report "fixed." Whether it actually took effect is a separate question. Log in as B, open A's URL. An error means it's closed.
This takes one minute and is the only way to determine whether the fix genuinely landed. "The AI said it fixed it" is not something to trust in this area.
Summary
- Authentication (who) and authorization (may you) are different jobs. AI omits authorization
- One digit changed in a URL revealing someone else's data is the textbook hole OWASP ranks first among API risks
- The list is filtered correctly while the detail is wide open — that asymmetry is why testing never catches it
- You can check with a browser alone: two accounts, open one's URL as the other
- Hiding is not defending. Decide on the server, and pair it with RLS for two layers
Along with RLS, this is one of the four items to close before launch. The whole picture is in Before you launch the app you built with AI.