BOLA stands for Broken Object Level Authorization. It is OWASP’s #1 API security risk and one of the most common findings in AI-generated backend code. The vulnerability is not that the route is missing authentication — AI tools add that. It is that the route checks the user is logged in, but does not check that they own the specific record they are requesting.
A concrete example
// VULNERABLE: authenticated but not authorized for ownership
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await db.invoices.findById(req.params.id);
res.json(invoice); // returns any invoice to any logged-in user
});Any logged-in user can change the :id in the URL and read any other user’s invoice. The route is not broken — it does exactly what it was written to do. It was just never written to check ownership.
Scan your own app for issues like these
Paste your live URL. We check what your app serves publicly for exposed keys and misconfigurations. No account, no install.
Why it is OWASP API Security #1
BOLA tops the list because it is simultaneously easy to introduce and trivial to exploit. No special tooling required — just change the ID in the URL and see what comes back. It affects read, update, and delete operations. And it generates no errors: the route responds normally with a 200, so it does not show up in any error monitoring.
Why AI tools ship it constantly
AI coding tools (Cursor, Lovable, Bolt, Claude Code) add the auth middleware because it is a well-defined pattern they have seen thousands of times. The ownership check is more contextual — it requires knowing which field on the model maps to which field on the session user — and in a fast-scaffolded endpoint that knowledge often gets dropped. The endpoint satisfies the feature requirement (retrieve invoice by ID) without the security requirement (only the invoice owner can retrieve it).
The fix
// CORRECT: scope the query to the authenticated user
app.get('/api/invoices/:id', requireAuth, async (req, res) => {
const invoice = await db.invoices.findOne({
_id: req.params.id,
userId: req.user.id, // must belong to the requesting user
});
if (!invoice) return res.status(404).json({ error: 'Not found' });
res.json(invoice);
});Return 404, not 403, when the record is not found or does not belong to the user — do not confirm the record exists for other users. Apply the same ownership filter to PUT, PATCH, and DELETE routes.
How to audit your codebase for BOLA
- List every route that takes an ID in the URL path, query string, or request body
- For each one, check whether the database query scopes results to the authenticated user
- Test manually: log in as user A and request user B’s resource ID — you should get a 404
- Pay extra attention to routes that were quickly scaffolded or generated by an AI tool, especially CRUD endpoints
BOLA is the authorization equivalent of missing auth entirely. See missing auth on an API route for the authentication check that comes before the ownership check, and the full vibe-coding security checklist for both passes together.
Common questions
What is BOLA in web security?
BOLA stands for Broken Object Level Authorization. It means an API endpoint confirms the caller is authenticated — has a valid session or token — but does not confirm they own the specific resource they are requesting. The endpoint is not broken; it was just never written to check ownership. Any logged-in user can read, modify, or delete any other user's data by changing the ID in the request.
What is the difference between BOLA and IDOR?
IDOR (Insecure Direct Object Reference) is the older OWASP name for essentially the same class of vulnerability. BOLA is the current OWASP API Security Top 10 terminology (API1:2023). Both describe the same gap: the app exposes an internal object identifier and does not verify the caller owns that object. BOLA is the preferred term for API context; IDOR often appears in web app context.
Why do AI tools produce BOLA so often?
AI tools generate the auth middleware because they have seen that pattern thousands of times in public code. The ownership check is more context-dependent — it requires knowing which field on the resource maps to which field on the authenticated user — and that nuance gets dropped in fast-scaffolded CRUD endpoints. The route works correctly for the feature being built; the ownership check is only needed to prevent abuse by other users.
How do I test for BOLA in my API?
Create two user accounts. Log in as user A and make requests to resources owned by user B by substituting user B's resource IDs into the request. If user A can read, update, or delete user B's records, you have BOLA. Focus on any route that takes an ID in the URL path, query parameter, or request body and returns or mutates a specific record.
How do I fix a BOLA vulnerability?
Scope every database query that fetches a record by ID to include the authenticated user's ID as an additional filter: db.invoice.findOne({ _id: params.id, userId: session.userId }). If no record is found — either because it doesn't exist or because it belongs to another user — return 404, not 403, to avoid confirming the record exists. Apply the same pattern to update and delete routes.