Broken Access Control Is Now the #1 Security Risk
When OWASP last updated its Top 10, broken access control jumped to the number one position—and for good reason. OWASP found access control failures in **94% of the applications they tested.** It remains the single most common, most exploited class of web vulnerability, and AI-generated code is making the problem dramatically worse.
The most common form of broken access control is IDOR: Insecure Direct Object Reference. It is cheap to introduce, easy to miss in review, and devastating when exploited. And it is exactly the kind of bug AI assistants produce by default.
What Is IDOR (and How It Relates to BOLA)?
IDOR is an access control flaw where an application exposes a reference to an internal object—a database record, a file, a user ID—without verifying that the requester is actually allowed to access it.
The classic example: you log in and visit `/api/invoices/1043`. You see your invoice. Then you change the number to `/api/invoices/1044` and see *someone else's* invoice. The API authenticated you (it knows who you are) but never authorized you (it never checked whether this invoice is yours).
You will also see this called **BOLA (Broken Object Level Authorization)**—the term OWASP uses in its API Security Top 10. IDOR and BOLA describe the same underlying failure: missing per-object ownership checks.
Why AI-Generated Code Is So Prone to IDOR
AI models are trained on mountains of tutorial code, and tutorials almost always skip authorization to stay readable. So when you ask an assistant to "build an endpoint that returns an order by ID," it gives you exactly that—and nothing more:
// ❌ AI-generated: authenticated, but NOT authorized
app.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await Order.findById(req.params.id)
res.json(order) // Returns ANY order to ANY logged-in user
})
The code looks complete. It compiles, it passes a happy-path test, and it ships. But there is no check that `order.userId` matches `req.user.id`. Every authenticated user can read every order by walking the IDs.
This is the AI security paradox in miniature: the code works, so it feels done—while a critical access control hole sits in plain sight.
A Real-World IDOR: Cross-Tenant Data Exposure
In 2026, a broken access control advisory in a popular AI application showed the pattern at scale. Authenticated teams could access applications belonging to *other* teams simply by supplying a foreign `appId`. The API correctly validated each team's token, but never verified that the requested application actually belonged to the authenticated team—leading to cross-tenant data exposure.
That is the multi-tenant nightmare: one missing ownership check turns a per-user bug into a whole-company data breach.
Spotting IDOR in Your Codebase
Look for any endpoint that takes an ID from the request and fetches a record without scoping it to the current user. Compare the two patterns below.
**Vulnerable:**
// ❌ No ownership check
app.get('/api/documents/:id', requireAuth, async (req, res) => {
const doc = await Document.findById(req.params.id)
res.json(doc)
})
**Fixed:**
// ✅ Query scoped to the authenticated user
app.get('/api/documents/:id', requireAuth, async (req, res) => {
const doc = await Document.findOne({
_id: req.params.id,
ownerId: req.user.id, // the ownership check
})
if (!doc) return res.status(404).json({ error: 'Not found' })
res.json(doc)
})
How to Fix IDOR: The Ownership Check Pattern
#
1. Always Ask "Does This User Own This Resource?"
Before returning any object tied to a user, tenant, or organization, verify ownership. Make it a non-negotiable rule for every handler that accepts an ID.
#
2. Centralize Authorization Logic
Scattered, copy-pasted permission checks are where IDOR creeps back in. Put authorization in one middleware or policy layer so every route enforces it the same way. Inconsistent checks are the root cause of most access control oversights.
#
3. Use Unguessable Identifiers
Sequential integer IDs make enumeration trivial. Prefer UUIDs or other random identifiers so attackers cannot simply increment a number—though remember this is defense in depth, not a substitute for a real ownership check.
#
4. Scope Every Database Query to the User
The most robust fix is to make it *impossible* to fetch another user's data: include the owner constraint directly in the query (`ownerId: req.user.id`) rather than fetching first and checking later.
How to Test for IDOR Before You Deploy
IDOR only shows up when the app is running, which is why static review alone misses it. Test it dynamically:
Automating this cross-account probing is exactly the kind of dynamic testing that catches access control bugs traditional unit tests sail right past.
How DeployReady Detects Broken Access Control
DeployReady probes your running application, not just your source. It exercises authentication and authorization flows, attempts to access resources across user boundaries, checks for exposed admin routes, and flags endpoints that return data without proper ownership checks.
npx deployready@latest analyze ./my-app
✦ Testing localhost app...
🔴 CRITICAL: /api/orders/:id returns records without ownership check (IDOR)
🔴 CRITICAL: Admin route /admin/users reachable without authorization
🟡 WARNING: Sequential integer IDs enable resource enumeration
Production Readiness Score: 38 / 100
You get the finding, the affected route, and the fix guidance before the bug becomes a breach.
The Bottom Line
Broken access control is the #1 OWASP risk, IDOR is its most common form, and AI assistants generate it by default because they copy authorization-free tutorial code. The fix is simple to state and must be applied everywhere: authenticate the user, then authorize the object. Scope every query, centralize your checks, and test across accounts before you ship.
npx deployready@latest analyze .
Resources
---
**Not sure if your AI-generated API has an IDOR hole?** [Scan it with DeployReady](https://www.npmjs.com/package/deployready) or [book a security check](https://www.belsoftsolutions.com/meeting).