If you’ve built a SaaS with AI — Cursor, Claude, v0, whatever — there’s a real chance this exact gap is sitting in your codebase right now. It isn’t a typo and it won’t throw an error. Everything works. That’s what makes it dangerous.
Here’s the scenario, the code, why your tests will never catch it, and the one line that fixes it.
What you shipped
You asked the AI for an endpoint that returns a user’s order. It gave you something clean — it even added an authentication check, so you have to be logged in to call it. You tested it, it returned your order, you shipped.
What you actually shipped is an endpoint where any logged-in user can read every other user’s orders — names, shipping addresses, amounts paid — by changing a single number in the URL.
The code, and the thing it forgot

Read it once. The auth check is real: no session, no data. But look at the query. It fetches the order whose id matches the one in the URL — and that’s all it checks.
It confirms you’re logged in. It never confirms the order is yours.
Those are two different questions, and AI-generated code conflates them constantly. Authentication means the system knows who you are. Authorization means you’re allowed to touch this specific thing. The second check is the one that’s missing — and it’s the one that matters here.
How an attacker finds it — no tools required
This is the part that should worry you: exploiting this takes no skill and no special software. Your own order sits at /api/orders/48. So someone opens their browser, sees the number, and tries 47. Then 46. Then a five-line script counts from 1 upward.
GET /api/orders/1 → someone's order
GET /api/orders/2 → someone else's
GET /api/orders/3 → ...all of them
Every request comes back 200 OK with real customer data. This pattern has a name — IDOR, an insecure direct object reference, a form of broken access control — and it sits near the top of every list of the most common web vulnerabilities. In AI-generated apps specifically, it’s the single most frequent gap.
Why your tests pass anyway
Here’s why it slips past every check you’d normally trust. You test the endpoint as yourself: logged in as you, requesting your own order. It comes back correctly. Green. Ship it.
The bug needs a second user to exist before it does anything visible. With one account — yours — there’s nothing to leak. So every manual test, every demo, every “works on my machine” passes cleanly.
The hole opens the moment your first real customer signs up. By then it’s already in production, and you have no reason to suspect it, because nothing is broken. The endpoint just quietly answers questions it should be refusing.
Why AI writes this every single time
It’s worth understanding why, because it tells you where else to look.
AI optimizes for the happy path — the shortest code that satisfies the request you typed. “Return the order with this id” becomes a query that returns the order with that id. Done. It works.
What it doesn’t do is reason adversarially: what if someone passes an id that isn’t theirs? That question wasn’t in your prompt, so it isn’t in the answer. The model isn’t being careless — it’s being literal. And the same model that writes the gap won’t flag the gap, because as far as it’s concerned, the code is finished and correct.
The fix is one line
Scope the query to the owner:

.eq('id', params.id)
.eq('user_id', user.id) // only rows that belong to you
Now the query can only ever return rows belonging to the logged-in user. Ask for someone else’s order id and you get nothing back. That single condition is the entire difference between “works” and “safe.”
Don’t stop at one endpoint — check your whole app
The same omission repeats across a codebase. Anywhere your app returns, updates, or deletes a record by id, the same question applies. Spend ten minutes and go through every route that touches user data, asking the one thing the AI didn’t:
- Read — does the query filter by owner, or just by id? (
GET /api/orders/:id,/api/messages/:id,/api/files/:id) - Update — before writing, does it confirm the record belongs to the caller? Or does it update any row with a matching id?
- Delete — same question, higher stakes.
- The shortcut test — log in as a second test user, then try to open the first user’s records by id. If anything comes back, you’ve found one.
If the answer to any of these is “it checks they’re logged in,” that is not the same as “it checks they’re allowed.”
The bigger problem
None of this is hard. A senior developer catches it in two seconds — it’s muscle memory. The problem isn’t difficulty; it’s that if nobody senior reads the code, nobody catches it. Not you, because it works. Not the AI, because to it the code is done. The author and the reviewer are the same model, with the same blind spots.
That’s the gap Velify is built to close. It reads your project and tells you what’s missing, why it matters, and how to fix it — in the plain language of a senior dev looking over your shoulder, not a wall of scanner output. No terminal.
If you’re shipping AI-generated code, run it before your first customer does.
