Home / Blog / Broken object level authorization
Findings and teardowns
Broken object level authorization (BOLA): examples, testing and fixes
BOLA is the flaw where changing one ID in a request returns someone else's data. Here is how it happens, how to test for it, and the ownership check that fixes it.
Cyberlop Labs · Updated · 15 min read
Broken object level authorization (BOLA) is an API flaw where a request names an object, such as an invoice or order, and the server returns or changes it without checking that the signed-in account owns it. It is OWASP API1:2023. Test it with two accounts and swapped IDs, and fix it with server-side ownership checks.
- BOLA means the server trusted an object ID from the client instead of checking that the signed-in account owns that object.
- BOLA and IDOR describe one weakness (MITRE lists them as alternate terms for CWE-639); BOLA is the API-era name and covers bodies, headers and GraphQL, not just URLs.
- Test with two accounts at the same level: send account A's session with account B's IDs to every endpoint that names an object, including writes and deletes.
- Fix it on the server: scope the query to the caller, keep one policy function, and take identity from the session, never the request body.
- Unguessable IDs slow guessing but are not the fix; OWASP and CISA both pair them with access checks.
- Keep it fixed with two-account regression tests in CI and a retest that replays the original exploit.
Broken object level authorization (BOLA) is a flaw where an API or web app reads, changes or deletes a record because the request names it, without checking that the signed-in account is allowed to touch that specific record. It is number one on the OWASP API Security Top 10 (2023). The fix is a server-side ownership check on every object a request names.
Being signed in is not the same as being allowed. A valid session proves who is asking, and says nothing about whether invoice 4821 belongs to them. Below you will find realistic examples, the line between BOLA and IDOR, an afternoon test plan, the fix, and the code-review habits that keep the flaw from coming back.
What is broken object level authorization (BOLA)?
BOLA is an authorisation failure at the level of a single object: one invoice, one order, one project, one file. OWASP describes the risk in API1:2023 as attackers manipulating object identifiers (sequential integers, UUIDs or strings) in API requests to reach objects that belong to other users. The server trusts the ID the client sent instead of working out who is calling and what they own.
It is one member of the broken access control family. Authentication worked, because the caller really is who they say they are. Authorisation did not, because nothing asked whether this caller may use this object. The result is horizontal privilege escalation: one account reaching data that belongs to another account at the same level.
BOLA in one request
Customer A is signed in and opens their own invoice. The app calls GET /api/invoices/4821. Customer A changes the number to 4822 and sends it again. If the API answers with someone else's invoice, that is BOLA. Nothing was guessed, bypassed or forged. The request was well formed and the session was valid.
Where BOLA sits in the standards
The same weakness has several names, and it helps to know them when you read a report or a questionnaire.
Caption: the names for one weakness, and where each comes from.
| Name | Where it comes from | What it says |
|---|---|---|
| API1:2023 Broken Object Level Authorization | OWASP API Security Top 10, 2023 | Object IDs in requests are trusted, so one account reaches another account's objects. |
| A01:2025 Broken Access Control | OWASP Top 10, 2025 | The wider category. Insecure object references are listed as a common failure. |
| CWE-639 | MITRE: Authorization Bypass Through User-Controlled Key | Lists IDOR and BOLA as alternate terms. A child of CWE-863 (Incorrect Authorization). |
| CWE-862 | MITRE: Missing Authorization | No authorisation check at all when an actor reaches a resource or action. Often the root cause behind BOLA. |
| IDOR | Older web-application term | The same idea, described from the web page and URL side. |
MITRE's CWE-639 describes the failure as a system that does not stop users reaching another user's data by changing the key that identifies a record, and names both IDOR and BOLA as alternate terms.
How common is BOLA, and why does it keep happening?
BOLA is widespread and easy to find. OWASP rates API1:2023 as easy to exploit, widespread in prevalence and easy to detect, with moderate technical impact. A flaw that is easy to exploit, easy to detect and common will be found by a stranger before it is found by you.
The wider category is the most tested-for failure in web security. In the OWASP Top 10:2025, A01 Broken Access Control maps 40 CWEs and records 1,839,701 occurrences and 32,654 CVEs in the data. Its average incidence rate is 3.74%, and its maximum incidence rate is 20.15%.
MITRE's 2025 CWE Top 25 shows the same family near the top. CWE-862 Missing Authorization ranks fourth with a score of 13.28. CWE-863 Incorrect Authorization ranks 17th (4.14), CWE-284 Improper Access Control 19th (4.07) and CWE-639 24th (2.62).
The business case is plain. Verizon's 2026 Data Breach Investigations Report summary says 31% of breaches now start with software vulnerabilities, beating stolen passwords as the top way in. That figure covers all software vulnerabilities, not BOLA alone, but it is the pool BOLA belongs to. Joint guidance from the US, Australian and NSA agencies adds that this class of flaw is "common and hard to prevent outside the development process", and that attackers can find and use it at scale with automated tools.
Why developers keep writing it
BOLA is rarely a typo. It is a decision nobody made, and it usually goes missing for one of four reasons.
- The framework does not know your rules. A router can check that a token is valid. Only your code knows that invoices belong to accounts.
- The front end hid the problem. The UI only ever links to your own records, so the team never sees another account's ID in a test.
- Checks live in controllers, one by one. The twentieth endpoint gets written on a Friday and the check is forgotten.
- Tests use one account. A single-account test suite passes whether or not the ownership check exists.
That last point is why a scanner with one login struggles here, and why proof of exploitation matters more than a suspicion.
What does BOLA look like in a real application?
BOLA appears wherever a request names an object: in the path, the query string, the JSON body, a header or a GraphQL argument. The five examples below are illustrative composites, not reports about any real company. Each one is a pattern that review teams meet again and again.
Example 1: the invoice read
A billing endpoint, GET /api/invoices/{id}, loads the invoice by ID and returns it. The developer checked that a token exists. Customer A reads customer B's invoice, including name, address and amounts. This is the textbook case and the one most reports describe.
Example 2: the write that nobody reviewed
The read endpoint is protected. The matching PATCH /api/orders/{id}/address is not, because it was added later by a different developer. Account A changes the delivery address on account B's order. Write and delete BOLA is often worse than read BOLA because it changes state. OWASP's own third scenario is a GraphQL mutation that deletes a document by ID with no permission check.
Example 3: the parent is checked, the child is not
The path is /api/orgs/{orgId}/projects/{projectId}. The code confirms the caller belongs to orgId, then loads projectId without confirming that project sits inside that organisation. A member of organisation 1 supplies their own orgId and another organisation's projectId. This is a cross-tenant leak that passes a quick manual look, because the first check succeeds.
Example 4: the ID in the body
The request is POST /api/reports/export with {"accountId": 77}. The server trusts accountId from the body instead of using the account tied to the session. Nothing in the URL looks like an object reference, so path-based reviews and simple replay rules miss it.
Example 5: the identifier that is not an ID
OWASP's second scenario uses a vehicle identification number (VIN) in a mobile API that controls engine and doors, with no check that the VIN belongs to the caller. OWASP's first uses shop names in /shops/{shopName}/revenue_data.json. Names, VINs, email addresses, order references and file names are all object keys. If a caller can type one, the server must check it.
Variants to look for
- Bulk endpoints that take an array of IDs and check only the first.
- Search, list and export endpoints that filter by a client-supplied owner field.
- Download links for files, receipts or reports that work for any signed-in session.
- Nested objects, such as comments on a ticket, where the parent ticket is checked and the comment is not.
- Older API versions (
/v1/) still routed and still missing the newer check.
What is the difference between BOLA and IDOR?
For most teams, BOLA and IDOR describe the same bug. IDOR (insecure direct object reference) comes from web-application testing. BOLA is the name OWASP uses for APIs. MITRE lists them as alternate terms for one weakness, IDOR and BOLA.
The practical difference is scope and habit. The IDOR label tends to bring to mind an ID in a URL. BOLA is wider: it covers every place an API accepts an object reference, including bodies, headers and GraphQL arguments, and both reading and writing. If you are writing a test plan, use the wider idea.
BOLA, BFLA and mass assignment: which flaw is it?
Three neighbouring problems are often mixed up. The question to ask is what the caller is doing wrong: reaching the wrong object, calling the wrong function, or setting the wrong field.
Caption: how to tell BOLA from its neighbours.
| Flaw | The caller is... | Example | Fix lives in |
|---|---|---|---|
| BOLA (API1) | Reaching someone else's object with a function they may use | Reading /invoices/4822 that belongs to another account | The ownership check on the record |
| BFLA (API5) | Calling a function their role should not reach | A normal account calling /api/admin/v1/users/all | The role check on the endpoint |
| Mass assignment | Setting a field they should not control | Sending "role":"admin" in a profile update | The allowed-fields list on the input |
OWASP's API5:2023 draws the same line: object-level authorisation restricts access to specific data instances, function-level authorisation restricts access to operations. Real systems usually need both.
How do you test for BOLA?
To test for BOLA, create two separate accounts, record the object IDs each one owns, then send account A's session with account B's object IDs to every endpoint that names an object. If anything other than a refusal comes back, you have a finding. The OWASP Web Security Testing Guide has a dedicated test for insecure direct object references in its authorisation chapter, and the steps below follow the same idea.
Only run this on systems you own or have written permission to test, and use test accounts with test data. Never use real customers' records to prove a point.
Step by step
- Create two accounts at the same level. Two ordinary customers in two different organisations is the minimum. Add a second pair if the app has tiers such as member and admin.
- Create data with each account. Make an invoice, an order, a project, an upload and a comment under each. Write down every ID, name and reference you can see.
- Inventory the endpoints that take an object. Use the API's OpenAPI file, your proxy history, the mobile app's traffic and the front-end code. Note the verb, the path, and where the reference sits (path, query, body, header).
- Replay with swapped references. Send account A's token with account B's IDs. Do it for read, update, delete and every action verb such as export, share, approve or cancel.
- Compare the responses. A correct server refuses. Record anything that returns the object, changes it, or leaks a field from it.
- Test the lists. Check search, list, export and report endpoints for results that include another account's rows.
- Repeat across versions and clients. Try
/v1/,/v2/, the mobile endpoints and any GraphQL schema. - Save each hit as a repeatable request. Keep the request, the response and the account used, so the developer can reproduce it in a minute.
Where object references hide
Most missed BOLA comes from testing only the path. Use this table to widen the search.
Caption: places to look for an object reference, and what to swap.
| Location | Example | What to try |
|---|---|---|
| URL path | /api/invoices/4821 | Replace with an ID owned by the other account |
| Query string | ?orderId=991 | Swap the value; add a second orderId to see which one wins |
| JSON body | {"accountId": 77} | Change the owner or parent field; send IDs in nested objects |
| Header or cookie | X-Account-Id: 77 | Change it and see whether the server trusts it over the session |
| GraphQL argument | node(id: "...") | Query and mutate other accounts' nodes; try aliases and batched queries |
| Bulk or array input | {"ids":[1,2,3]} | Mix your IDs with another account's IDs |
| File or download link | /files/report-2026.pdf | Open another account's link with your session |
Reading the results
A 200 with the other account's data is a clear failure. A 200 with an empty body can also hide a write that succeeded, so check the object afterwards. A 403 or 404 is a pass for that request, though many teams prefer 404 so the response does not confirm that the object exists. Watch for differences in timing or message text between "not yours" and "does not exist", because those let a caller enumerate valid IDs.
Do random IDs make testing pointless?
No. Unpredictable IDs make guessing hard, not access impossible. IDs leak through emails, shared links, logs, list endpoints and other users' pages. Test with a real known ID from the other account, exactly as you would with a sequential one. The IDOR testing term and the IDOR checklist go deeper on this, and the API security testing guide places BOLA inside the full OWASP API list.
How do you fix broken object level authorization?
You fix BOLA by deciding access on the server from the authenticated identity, not from the ID the client sent. Load the object only through a query scoped to the caller, or load it and check ownership before using it, and deny by default if there is no clear yes. OWASP's prevention list for API1 says to enforce authorisation checks in every function that accesses records using client input.
Scope the query to the owner
The safest pattern never loads an object the caller cannot have. Instead of "find invoice 4822", ask "find invoice 4822 where the account is the caller's account". If nothing comes back, return 404. There is no separate check to forget, because the database filter is the check. OWASP's IDOR prevention cheat sheet recommends the same shape: determine the authenticated user from session information and scope queries to the data they can reach.
Use one policy function, not twenty copies
Where a query filter is not enough (shared documents, team roles, delegated access), put the rule in one place. A single function such as canView(account, invoice) is called by every endpoint, so reviewers read it once and tests cover it once. The ownership check entry explains the pattern. Moving to role-based or attribute-based rules does not remove this need: roles say what a person may do, ownership says which objects the action applies to.
Use the session, not the body, for who is calling
Fields such as accountId, userId or tenantId in a request body should be ignored for access decisions. Take them from the verified token or session. Where an endpoint is only ever about the caller's own data, expose it as "my" data (/api/me/invoices) so there is no ID to tamper with. OWASP's Authorization cheat sheet makes the same point: retrieve objects based on the authenticated user's identity where feasible, and validate permissions on every request.
Add a database backstop
For multi-tenant products, row-level security in the database adds a second wall. If a developer forgets the filter in one query, the database still refuses to return another tenant's rows. It does not replace application checks, but it turns a leak into an empty result.
Do not treat unguessable IDs as the fix
OWASP lists random, unpredictable identifiers among prevention strategies, and the joint CISA, NSA and ACSC advisory suggests cryptographically strong random values such as UUIDs. Both pair that with access checks. The IDOR cheat sheet is direct: even with complex identifiers, access control checks are essential. Use UUIDs to slow guessing and reduce accidental exposure, never as the decision.
A fix that holds, and one that does not
Caption: common patches compared on whether they close the hole.
| Patch | Closes BOLA? | Why |
|---|---|---|
| Query filtered by caller's account | Yes | The object is never loaded for the wrong caller. |
| Central policy function on every endpoint | Yes | One reviewed rule, applied the same way each time. |
| Switch sequential IDs to UUIDs | No | Slows guessing. A leaked or shared ID still works. |
| Hide the link in the UI | No | The API still answers anyone who sends the request. |
| Check only the parent object | Partly | Misses the case where the child belongs to a different parent. |
| Rate limiting alone | No | Slows enumeration, does not stop one targeted request. |
How do you prevent BOLA in code review?
Prevent BOLA in review by asking the same four questions about every endpoint that accepts an identifier: where does the ID come from, who owns the object, where is that checked, and what does a failed check return. If a reviewer cannot point to the line that answers the second question, the change is not ready.
Code review questions for any endpoint that takes an ID
- Is the caller's identity taken from the verified session, never from the body or a header the client controls?
- Is the object loaded through an owner-scoped query, or is there a visible ownership check before use?
- For nested paths, is the child checked against the parent as well as the parent against the caller?
- Do write, delete and action endpoints have the same check as the read endpoint?
- Does a list, search or export endpoint filter by the caller, not by a client-supplied owner field?
- Is the default result a refusal when the policy cannot say yes?
- Is there a test with two accounts that proves the refusal?
New endpoint, new question
Add one line to your pull-request template: "Which object does this endpoint name, and where is ownership checked?" It costs seconds, and it makes the author state the assumption. Add it to your security champion notes too. A deny-by-default router makes forgetting harder.
Review signals that deserve a second look
- A repository method like
findById(id)with no account or tenant argument. - A controller that reads
req.body.accountIdoruserId. - An endpoint added through a generic CRUD helper that skips the usual middleware.
- A new API version copied from an old one before the old one was fixed.
- A background job or export that reads data by ID with a service account and no caller context.
How do you stop BOLA coming back?
You stop BOLA returning with tests that fail when the ownership check disappears, plus a retest that repeats the original exploit against the fixed code. A rescan that no longer flags the endpoint is weaker evidence than showing that the same request now fails.
Write the two-account test once
For each object type, add an automated test: account A creates an object, account B requests it and must be refused, and account B tries to change and delete it and must also be refused. Put the test in CI. Keep it small and fast, and treat a failure as a blocked build. This is a plain security regression test.
Retest with the original exploit
When a BOLA finding is fixed, replay the exact request that proved it. That is the point of a closed-loop retest: the fix is confirmed by the same exploit failing, not by a different scan passing. Related reading: the access control testing checklist for planning accounts and objects, and how to read a finding so a BOLA report gives your developers what they need.
Watch for it in production
Log object access with account and object IDs. A single account that reads hundreds of distinct objects it does not own, or a stream of 403 and 404 responses across sequential IDs, is worth an alert. Detection helps, but it does not replace the fix.
How Cyberlop checks for BOLA
Cyberlop checks cross-account access: one signed-in account reaching another account's data or functions. It reports only what it actually exploited, with the request, the response, a screenshot where a person can see the result, and, in white-box mode, the line of code where the missing check sits.
That matters for BOLA because the useful evidence is not "this endpoint looks like it takes an ID" but "this request returned another account's invoice, and this is the file and line that loaded it without an owner filter". When your team fixes it, the retest replays the exact exploit against the new code. See how it works, the evidence a finding carries, and the page for product teams. You can also run it on a schedule or in CI/CD and gate a build, which pairs with the regression tests above.
Questions people ask
Broken object level authorization: FAQs
Is BOLA the same as broken access control?
BOLA is one kind of broken access control. Broken access control (OWASP Top 10 A01:2025) is the wide category, covering missing checks on pages, functions, files and objects. BOLA is the specific case where the missing check is on one object, such as a record reached by changing an ID.
Is BOLA the same as IDOR?
In practice, yes. MITRE's CWE-639 lists IDOR and BOLA as alternate terms for the same weakness. IDOR comes from web testing and usually suggests an ID in a URL. BOLA is OWASP's API term and covers any object reference in a path, query, body, header or GraphQL argument.
Do UUIDs or random IDs prevent BOLA?
No. Random IDs make guessing hard, but an ID can still leak through emails, shared links, logs or list endpoints, and then it works for anyone. OWASP's IDOR cheat sheet says access control checks are essential even with complex identifiers.
Can a scanner find BOLA?
Rarely on its own. A scanner needs to know which account owns which object, and most run with one login, so a valid request for someone else's record looks like any other valid request. Testing with two accounts, by a person or a tool that holds both, is what exposes it.
What is the difference between BOLA and BFLA?
BOLA is about reaching the wrong object with a function you may use, such as reading another customer's invoice. BFLA (broken function level authorisation, OWASP API5:2023) is about calling a function you should not reach, such as an admin endpoint. The fixes differ: ownership checks for BOLA, role checks for BFLA.
Which status code should an API return when the object is not the caller's?
Either 403 or 404 is a pass. Many teams return 404 so the response does not confirm that the object exists. What matters is that the body, timing and message are the same for "not yours" and "does not exist".
How often should we test for BOLA?
Test whenever you add or change an endpoint that takes an object reference, and run your two-account regression tests on every build. Add a full authorisation test before major releases and after changes to roles, tenants or sharing features.
Is BOLA relevant for compliance?
Yes, indirectly. Frameworks such as SOC 2, ISO/IEC 27001 and PCI DSS expect access to data to be restricted to authorised people, and an auditor or customer questionnaire may ask for evidence of authorisation testing. A BOLA finding with proof, a fix and a retest is useful evidence.
Sources
Where the facts come from
- OWASP: API1:2023 Broken Object Level Authorization
- OWASP: API5:2023 Broken Function Level Authorization
- OWASP: Top 10:2025 A01 Broken Access Control
- MITRE: CWE-639 Authorization Bypass Through User-Controlled Key
- MITRE: 2025 CWE Top 25 Most Dangerous Software Weaknesses
- CISA, NSA and ACSC: Preventing Web Application Access Control Abuse (AA23-208A)
- OWASP: Insecure Direct Object Reference Prevention Cheat Sheet
- OWASP: Authorization Cheat Sheet
- OWASP: Web Security Testing Guide
- Verizon: 2026 Data Breach Investigations Report
Keep reading
Related articles
Access control testing checklist: sideways, upward and across tenants.
A step-by-step access control testing checklist for web apps and APIs: account planning, sideways, upward and cross-tenant checks, evidence and retests.
API security testing: a 2026 guide to the OWASP API Top 10
A step-by-step API security testing guide: the OWASP API Top 10 (2023) with test cases, BOLA and BFLA checks, JWT, OAuth, GraphQL and a CI/CD plan.
The IDOR checklist: how to test for insecure direct object references
An IDOR checklist for 2026: where object IDs hide, two-account tests, UUID and API cases, ownership-check fixes and regression tests.
Test your own ownership checks
Cyberlop checks cross-account access and reports only what it actually exploited, with the request, the response and, in white-box mode, the line of code. A fix is proven by replaying the exact exploit. Ask us to run it on one of your applications.
Or start with a $199 pilot on one application: thirty days, success criteria agreed before day one, credited against the annual if you convert.