September 2, 2026
My Autonomous Hunt Harness Found a Critical BOLA That Let Any Authenticated User Write to a Sharedβ¦
Introduction
By redhunter01
13 min read
Introduction
Some authorization vulnerabilities are difficult to find because the application appears to enforce access control correctly.
The UI shows only the objects belonging to the current user.
The API calls appear normal.
The user can interact with their own documents but doesn't see anything belonging to somebody else.
At that point, it's tempting to conclude that authorization is working.
My autonomous bug-hunting harness took a different approach.
Instead of testing authorization only through the UI, it analyzed the application's API behavior and asked a more fundamental question:
Does the server actually verify that the object ID supplied by the client belongs to the authenticated user?
During an autonomous authorization-testing workflow against a third-party compliance application, the harness identified a set of object-based API endpoints under:
/api/Entity/*/api/Entity/*The application appeared to implement vendor-level isolation in the client.
However, the server accepted client-supplied object identifiers without enforcing the corresponding ownership relationship.
This created a Broken Object-Level Authorization (BOLA) vulnerability.
A user with no assigned vendor/entity could obtain access to the shared production document library and, through the affected API, obtain capabilities to:
- Read shared library metadata
- Obtain an upload grant for the shared production library
- Create a file in that library
- Delete a document by object ID
The write/delete capability was safely demonstrated using only a benign probe file that I created and immediately deleted.
No real vendor document was modified or deleted.
The issue was assessed as Critical, with a CVSS 3.1 score of 9.1.
1. The Hunting Philosophy
The key idea behind my autonomous authorization workflow is simple:
Never assume the UI's authorization model is the server's authorization model.
A typical application might look like:
Browser
|
v
React UI
|
+---- "Show only my documents"
|
v
API
|
v
Backend
|
v
SharePoint / GraphBrowser
|
v
React UI
|
+---- "Show only my documents"
|
v
API
|
v
Backend
|
v
SharePoint / GraphThe UI might correctly hide objects that don't belong to the current user.
But if the backend does this:
POST /api/Entity/DeleteDocumentById?id=<client-supplied-id>POST /api/Entity/DeleteDocumentById?id=<client-supplied-id>and simply trusts the ID, the UI restriction becomes irrelevant.
The browser is not a security boundary.
The server must independently enforce:
Authenticated User
|
v
Authorized Entity
|
v
Requested ObjectAuthenticated User
|
v
Authorized Entity
|
v
Requested ObjectThat was the assumption my harness tested.
2. How the Autonomous Workflow Approached the Application
The authorization-hunting workflow roughly followed:
Target
|
v
Application discovery
|
v
API extraction
|
v
Endpoint classification
|
v
Object-ID identification
|
v
Authorization hypothesis
|
v
Authenticated baseline
|
v
Object substitution
|
v
Cross-object response comparison
|
v
Fallback/error-path analysis
|
v
Safe write validation
|
v
Safe delete validation
|
v
Manual confirmationTarget
|
v
Application discovery
|
v
API extraction
|
v
Endpoint classification
|
v
Object-ID identification
|
v
Authorization hypothesis
|
v
Authenticated baseline
|
v
Object substitution
|
v
Cross-object response comparison
|
v
Fallback/error-path analysis
|
v
Safe write validation
|
v
Safe delete validation
|
v
Manual confirmationThe important part was that the harness wasn't simply looking for HTTP 200 responses.
It was looking for relationships between the authenticated principal and the object being accessed.
3. Understanding the Application's Intended Authorization Model
The application was designed around vendor entities.
Conceptually:
User
|
v
Vendor Entity
|
v
Vendor Folder
|
v
Vendor DocumentsUser
|
v
Vendor Entity
|
v
Vendor Folder
|
v
Vendor DocumentsThe intended authorization rule appeared to be:
A vendor should only be able to access objects belonging to that vendor's entity.
The production document store, however, used a shared SharePoint library with separate folders for vendors.
Conceptually:
Production Library
β
βββ Vendor A
β βββ Document 1
β βββ Document 2
β
βββ Vendor B
β βββ Document 1
β βββ Document 2
β
βββ Vendor C
β βββ ...
β
βββ ...Production Library
β
βββ Vendor A
β βββ Document 1
β βββ Document 2
β
βββ Vendor B
β βββ Document 1
β βββ Document 2
β
βββ Vendor C
β βββ ...
β
βββ ...The critical question was:
Where is the vendor-to-folder authorization relationship actually enforced?
The investigation showed that the application relied heavily on client-side behavior to maintain that relationship.
4. The First Interesting Signal
The harness identified several API endpoints under:
/api/Entity/*/api/Entity/*including operations conceptually equivalent to:
GetEntityInfoAndDDQById
GetRootWhenError
CreateUploadSession
DeleteDocumentByIdGetEntityInfoAndDDQById
GetRootWhenError
CreateUploadSession
DeleteDocumentByIdSeveral of these endpoints accepted object identifiers supplied by the client.
For example:
?id=<object-id>?id=<object-id>or:
{
"fileId": "<object-id>"
}{
"fileId": "<object-id>"
}This immediately created a BOLA hypothesis:
Authenticated User
|
v
Client-supplied object ID
|
v
Does the server verify ownership?Authenticated User
|
v
Client-supplied object ID
|
v
Does the server verify ownership?5. Testing an Account With No Entity
A particularly useful test case was an authenticated account that did not have an assigned vendor entity.
This was important because such an account should logically have:
User
|
X
No vendor entity
|
X
No vendor folder
|
X
No vendor documentsUser
|
X
No vendor entity
|
X
No vendor folder
|
X
No vendor documentsIf the authorization model was correctly enforced server-side, there should be no meaningful production document scope available to this account.
Instead, the API behavior revealed something unexpected.
6. The Error Path Was the First Break in the Model
One of the first interesting behaviors involved an error/fallback flow.
The request:
GET /api/Entity/GetEntityInfoAndDDQById?id=00000000-0000-0000-0000-000000000000GET /api/Entity/GetEntityInfoAndDDQById?id=00000000-0000-0000-0000-000000000000produced an error response.
That by itself wasn't interesting.
But the harness recognized that the application had a secondary fallback endpoint:
GET /api/Entity/GetRootWhenErrorGET /api/Entity/GetRootWhenErrorCalling the fallback returned the shared production library root.
The response exposed a root object similar to:
id = 01TKBTINVZN4IZAU3KEZEIUHM2BHQJWBR6id = 01TKBTINVZN4IZAU3KEZEIUHM2BHQJWBR6with a child count of approximately:
14941494That was the first strong indication that the user's effective scope was not limited to their own entity.
The application was returning the shared production root.
7. Why the Fallback Endpoint Mattered
This was an important lesson in the hunt.
Authorization testing shouldn't focus only on the obvious success paths.
Applications frequently have:
Normal path
Error path
Fallback path
Retry path
Legacy path
Debug pathNormal path
Error path
Fallback path
Retry path
Legacy path
Debug pathDevelopers may protect the normal path while forgetting that a fallback endpoint exposes the same underlying resource.
Conceptually:
Normal request
|
v
Authorization
|
v
User's entityNormal request
|
v
Authorization
|
v
User's entitybut:
Error
|
v
Fallback
|
X
Authorization missing
|
v
Shared rootError
|
v
Fallback
|
X
Authorization missing
|
v
Shared rootThe fallback became an authorization boundary of its own.
8. From Read to Write: Testing the Authorization Boundary Safely
Once the shared production root was identified, the next question was:
Does the server merely expose metadata, or can the same authorization flaw affect write operations?
I did not test this by modifying a real vendor document.
Instead, I used a clearly labeled benign probe.
The upload endpoint accepted a client-supplied fileId:
POST /api/Entity/CreateUploadSessionPOST /api/Entity/CreateUploadSessionwith a body conceptually equivalent to:
{
"fileName": "<probe-file>",
"fileId": "01TKBTINVZN4IZAU3KEZEIUHM2BHQJWBR6"
}{
"fileName": "<probe-file>",
"fileId": "01TKBTINVZN4IZAU3KEZEIUHM2BHQJWBR6"
}The supplied ID was the shared production root rather than an entity owned by the current user.
The server responded with an upload session.
That was the critical authorization signal.
9. The Server Granted a Privileged Upload Capability
The upload response contained a pre-authenticated Microsoft Graph/SharePoint upload URL.
The important security boundary was:
User
|
v
Application API
|
X
Ownership check missing
|
v
Privileged service identity
|
v
Production SharePoint libraryUser
|
v
Application API
|
X
Ownership check missing
|
v
Privileged service identity
|
v
Production SharePoint libraryThe application was acting as a privileged intermediary.
This is a particularly important pattern in modern applications.
The authenticated user does not necessarily have direct SharePoint permissions.
Instead:
User β Application β Graph β SharePointUser β Application β Graph β SharePointThe application service principal has the powerful permissions.
Therefore, if the application fails to enforce object-level authorization before calling Graph, the user can potentially inherit a much broader capability than they were intended to have.
10. Completing the Safe Write Test
To verify that the upload capability wasn't merely theoretical, I completed the upload using a benign probe file.
The server-side result was:
HTTP 201 CreatedHTTP 201 CreatedThe production library's child count increased.
The sequence was:
Authenticated account
|
v
Shared root ID supplied
|
v
Upload session granted
|
v
Benign probe uploaded
|
v
HTTP 201
|
v
Library count increasedAuthenticated account
|
v
Shared root ID supplied
|
v
Upload session granted
|
v
Benign probe uploaded
|
v
HTTP 201
|
v
Library count increasedThis established an actual write primitive.
11. Testing the Delete Capability
The next question was whether object-level authorization was also missing from deletion.
The relevant endpoint accepted a document ID:
POST /api/Entity/DeleteDocumentById?id=<driveItemId>POST /api/Entity/DeleteDocumentById?id=<driveItemId>Again, I did not delete a real vendor document.
I supplied the ID of the benign probe file that I had just created.
The API returned:
HTTP 200HTTP 200The library child count decreased back to its original value.
The complete sequence was:
Baseline
|
| childCount = N
v
Create benign probe
|
| childCount = N + 1
v
Delete probe
|
| childCount = N
v
Net-zero footprintBaseline
|
| childCount = N
v
Create benign probe
|
| childCount = N + 1
v
Delete probe
|
| childCount = N
v
Net-zero footprintThis was strong evidence that the authorization problem affected not only read behavior, but also write and delete operations.
12. The Full End-to-End Authorization Failure
The complete verified chain was:
Authenticated user
|
v
No assigned vendor entity
|
v
Fallback endpoint
|
v
Shared production root exposed
|
v
Root ID supplied to upload endpoint
|
v
No ownership validation
|
v
Upload URL granted
|
v
Benign file created
|
v
Delete endpoint accepts object ID
|
v
Benign file deleted
|
v
Production state restoredAuthenticated user
|
v
No assigned vendor entity
|
v
Fallback endpoint
|
v
Shared production root exposed
|
v
Root ID supplied to upload endpoint
|
v
No ownership validation
|
v
Upload URL granted
|
v
Benign file created
|
v
Delete endpoint accepts object ID
|
v
Benign file deleted
|
v
Production state restoredThe key observation was:
The server trusted the object ID supplied by the client instead of deriving and enforcing the user's authorized object scope.
13. Why This Is BOLA
Broken Object-Level Authorization occurs when an application exposes an object reference but fails to verify whether the requesting user is authorized to access that object.
The vulnerable pattern looks like:
GET /api/document?id=123GET /api/document?id=123where the backend effectively does:
find(123)
return objectfind(123)
return objectinstead of:
object = find(123)
if object.owner != current_user:
deny()
return objectobject = find(123)
if object.owner != current_user:
deny()
return objectIn this case, the same principle applied to multiple object types:
fileId
driveItem ID
entity GUIDfileId
driveItem ID
entity GUIDThe server accepted these identifiers without consistently establishing:
Requested object
|
v
Belongs to
|
v
Authenticated user's entityRequested object
|
v
Belongs to
|
v
Authenticated user's entity14. Why the Service Principal Made the Impact Worse
The backend architecture added an important amplification factor.
The application used a privileged service identity to access the production SharePoint library.
Conceptually:
ββββββββββββββββββββ
β Authenticated β
β User β
ββββββββββ¬ββββββββββ
β
v
ββββββββββββββββββββ
β Web Application β
ββββββββββ¬ββββββββββ
β
Missing ownership
validation
β
v
ββββββββββββββββββββ
β Service Principal β
ββββββββββ¬ββββββββββ
β
v
ββββββββββββββββββββ
β SharePoint β
β Production Lib β
ββββββββββββββββββββββββββββββββββββββββ
β Authenticated β
β User β
ββββββββββ¬ββββββββββ
β
v
ββββββββββββββββββββ
β Web Application β
ββββββββββ¬ββββββββββ
β
Missing ownership
validation
β
v
ββββββββββββββββββββ
β Service Principal β
ββββββββββ¬ββββββββββ
β
v
ββββββββββββββββββββ
β SharePoint β
β Production Lib β
ββββββββββββββββββββThe user did not need direct SharePoint permissions.
The vulnerable application became the privileged bridge.
This is a common and important authorization pattern to look for when testing applications backed by:
- Microsoft Graph
- SharePoint
- S3
- Cloud storage
- Internal document repositories
- Service accounts
- Backend automation identities
15. Why Client-Side Authorization Is Not Enough
The application's React client appeared to understand the intended ownership model.
It could determine which vendor folder the current user should interact with.
But client-side restrictions are not security controls.
An attacker can modify:
fileId
entityGUID
driveItemId
folderId
documentIdfileId
entityGUID
driveItemId
folderId
documentIdbefore sending the request.
Therefore, the server must independently enforce:
current_user
|
v
authorized_entity
|
v
authorized_folder
|
v
requested_objectcurrent_user
|
v
authorized_entity
|
v
authorized_folder
|
v
requested_objectIf the API instead trusts:
fileId = whatever the client suppliedfileId = whatever the client suppliedthe authorization model can be bypassed.
16. The Autonomous Harness's Key Insight
The important discovery wasn't simply:
"Try changing the ID."
That's a standard BOLA technique.
The more interesting part was how the autonomous workflow chained multiple observations.
It effectively reasoned:
Object ID accepted
β
Fallback endpoint exists
β
Fallback exposes shared root
β
Root is not associated with current user
β
Another endpoint accepts fileId
β
Test same object reference
β
Upload session granted
β
Write capability confirmed
β
Another endpoint accepts driveItem ID
β
Test benign object
β
Delete succeedsObject ID accepted
β
Fallback endpoint exists
β
Fallback exposes shared root
β
Root is not associated with current user
β
Another endpoint accepts fileId
β
Test same object reference
β
Upload session granted
β
Write capability confirmed
β
Another endpoint accepts driveItem ID
β
Test benign object
β
Delete succeedsThis is exactly the type of multi-step relationship I wanted the autonomous hunting system to discover.
17. Testing the Impact Without Touching Vendor Data
One of the most important parts of this assessment was deciding how much exploitation was actually necessary.
The theoretical impact included the possibility of interacting with vendor documents belonging to other entities.
But proving that by modifying real compliance records would have been unnecessary and unsafe.
Instead, I used:
A clearly labeled test fileA clearly labeled test fileand verified:
Create β 201
Delete β 200Create β 201
Delete β 200with:
childCount:
N β N+1 β NchildCount:
N β N+1 β NThis provided a clean proof of write/delete capability while leaving the production library in its original state.
18. What Was Proven
The testing directly established:
Read
An authenticated user without an assigned entity could access the shared production library root and obtain its structure/count.
Write
The same user could obtain an upload capability for the shared production root and successfully create a file.
Delete
The user could delete the created object by supplying its object ID.
Therefore:
Object-level authorization
|
+---- Read β
+---- Write β
+---- Delete βObject-level authorization
|
+---- Read β
+---- Write β
+---- Delete βThe flaw was systemic rather than a single isolated endpoint issue.
19. What Was Deliberately Not Executed
The vulnerability potentially allowed access to specific vendor objects by supplying their identifiers.
However, I deliberately did not:
- Read real vendor documents in bulk
- Modify real vendor records
- Delete real vendor documents
- Overwrite vendor compliance files
- Plant malicious content in vendor folders
- Enumerate sensitive vendor data unnecessarily
Those actions weren't required to establish the authorization failure.
The benign probe was enough.
20. The Potential Production Impact
If an attacker obtained a valid object identifier belonging to another vendor, the missing authorization check could potentially allow operations against that object.
The affected capabilities could therefore include:
Read
β
Vendor compliance information
Write
β
Document tampering / malicious file placement
Delete
β
Destruction of compliance recordsRead
β
Vendor compliance information
Write
β
Document tampering / malicious file placement
Delete
β
Destruction of compliance recordsThe production library contained approximately:
1,493 vendor folders1,493 vendor foldersmaking the authorization failure potentially systemic rather than isolated to a single account.
Because the application operated through a privileged backend identity, the blast radius could extend across the shared production document store.
21. Why Self-Registration Was Not the Vulnerability
The application allowed users to create accounts through self-registration.
That behavior itself was not the vulnerability.
An authenticated user being able to interact with their own entity was also intended behavior.
Self-registration was relevant only because it established reachability:
Internet user
|
v
Account registration
|
v
Authenticated session
|
v
Vulnerable APIInternet user
|
v
Account registration
|
v
Authenticated session
|
v
Vulnerable APIThe actual vulnerability was:
The server failed to enforce object-level authorization after authentication.
This distinction is important when writing authorization reports.
Authentication and authorization are different security properties.
Authentication:
"Who are you?"
Authorization:
"What are you allowed to access?"Authentication:
"Who are you?"
Authorization:
"What are you allowed to access?"The application successfully authenticated the user.
It failed to consistently enforce the second question.
22. General BOLA Hunting Methodology
This finding turned into a useful methodology for testing object-level authorization.
Step 1 β Identify object references
Look for parameters such as:
id=
userId=
entityId=
fileId=
documentId=
folderId=
driveItemId=
resourceId=
guid=id=
userId=
entityId=
fileId=
documentId=
folderId=
driveItemId=
resourceId=
guid=Step 2 β Identify the intended ownership relationship
Determine:
User
β
Account
β
Organization / tenant / vendor
β
Folder
β
ObjectUser
β
Account
β
Organization / tenant / vendor
β
Folder
β
ObjectThe key question is:
What object is this user supposed to own?
Step 3 β Establish a baseline
Use an object that unquestionably belongs to the current user.
Confirm that the normal operation succeeds.
Step 4 β Substitute object identifiers
Change only the object reference.
For example:
Object A β Object BObject A β Object BKeep everything else constant.
This makes differential analysis much easier.
Step 5 β Test error and fallback paths
Don't stop with:
/api/document/api/documentLook for:
/api/document/fallback
/api/document/error
/api/document/retry/api/document/fallback
/api/document/error
/api/document/retryAuthorization bugs frequently appear in secondary paths.
Step 6 β Look for backend privilege escalation
Ask:
What identity does the server use when accessing the underlying resource?
If the application uses:
Service account
Service principal
Cloud role
Application identityService account
Service principal
Cloud role
Application identitythe impact of missing authorization can be substantially larger.
Step 7 β Test read/write/delete separately
Don't assume that because read is protected, write is protected.
Test authorization independently for:
GET
POST
PUT
PATCH
DELETEGET
POST
PUT
PATCH
DELETEwhere permitted.
Step 8 β Use a harmless object for write testing
If write access is suspected, create a benign test object when permitted.
Then verify:
Create
β
Observe
β
Delete
β
Restore baselineCreate
β
Observe
β
Delete
β
Restore baselineThis provides strong evidence without modifying real user data.
23. Lessons for Bug Bounty Hunters
Lesson 1 β Never trust the UI
If the UI says:
"You can only see your documents."
that means almost nothing from a security perspective.
The real question is:
What does the API do if I change the object ID?
Lesson 2 β Authentication is not authorization
A valid session does not mean that every object is accessible.
Always separate:
Can I call the endpoint?Can I call the endpoint?from:
Can I access this specific object?Can I access this specific object?Lesson 3 β Test object relationships
BOLA hunting becomes much more powerful when you understand the data model.
Don't randomly replace IDs.
Build the relationship:
User
β
Entity
β
Folder
β
DocumentUser
β
Entity
β
Folder
β
DocumentThen test whether the server enforces every relationship.
Lesson 4 β Error paths deserve attention
The fallback endpoint was particularly valuable.
Applications often receive more security attention on the happy path than on error handling.
A good hunter asks:
What happens when the normal authorization lookup fails?
Lesson 5 β Follow the privilege boundary
If the application calls Microsoft Graph using a service principal, AWS using an IAM role, or another privileged backend identity, investigate how authorization is enforced before that privileged call.
The service identity should never become an authorization bypass.
Lesson 6 β Think in capabilities
Don't just classify an endpoint as:
GET endpointGET endpointAsk what capability it grants:
Read
Create
Upload
Overwrite
Delete
Move
ShareRead
Create
Upload
Overwrite
Delete
Move
ShareAn authorization flaw becomes much more significant when it crosses multiple capabilities.
Lesson 7 β Prove impact safely
A real vendor document doesn't need to be deleted to demonstrate that the delete authorization check is missing.
A benign object can prove the same thing.
The goal is:
Maximum evidence with minimum impact.
24. How This Could Be Automated
A useful autonomous BOLA module could maintain an object graph:
User A
|
+---- Entity A
|
+---- Folder A
|
+---- Document A
User B
|
+---- Entity B
|
+---- Folder B
|
+---- Document BUser A
|
+---- Entity A
|
+---- Folder A
|
+---- Document A
User B
|
+---- Entity B
|
+---- Folder B
|
+---- Document BThen automatically test relationships such as:
User A β Document A β
User A β Document B ?
User B β Document A ?User A β Document A β
User A β Document B ?
User B β Document A ?For each request, the system can compare:
Status code
Response size
Object identifiers
Error messages
Metadata
Side effectsStatus code
Response size
Object identifiers
Error messages
Metadata
Side effectsThis allows the hunting engine to identify potential authorization inconsistencies without requiring a predefined list of exact vulnerable IDs.
The important part is the relationship model, not the payload list.
25. The Complete Discovery Chain
The autonomous discovery can be summarized as:
Autonomous API discovery
|
v
Object-based endpoints identified
|
v
Authorization model inferred
|
v
Authenticated account with no entity
|
v
Fallback/error path discovered
|
v
Shared production root exposed
|
v
Client-supplied object ID accepted
|
v
Ownership validation hypothesis
|
v
Upload endpoint tested with shared root
|
v
Upload session granted
|
v
Benign file created
|
v
Delete endpoint tested
|
v
Benign file deleted
|
v
Production state restored
|
v
Manual validation
|
v
Critical BOLA confirmedAutonomous API discovery
|
v
Object-based endpoints identified
|
v
Authorization model inferred
|
v
Authenticated account with no entity
|
v
Fallback/error path discovered
|
v
Shared production root exposed
|
v
Client-supplied object ID accepted
|
v
Ownership validation hypothesis
|
v
Upload endpoint tested with shared root
|
v
Upload session granted
|
v
Benign file created
|
v
Delete endpoint tested
|
v
Benign file deleted
|
v
Production state restored
|
v
Manual validation
|
v
Critical BOLA confirmed26. Root Cause in One Diagram
The intended architecture should have been:
Authenticated User
|
v
Determine authorized entity
|
v
Determine authorized folder
|
v
Verify requested object belongs to folder
|
v
Call SharePoint / GraphAuthenticated User
|
v
Determine authorized entity
|
v
Determine authorized folder
|
v
Verify requested object belongs to folder
|
v
Call SharePoint / GraphInstead, the vulnerable flow effectively behaved like:
Authenticated User
|
v
Client supplies object ID
|
v
Backend trusts object ID
|
X
Ownership check missing
|
v
Privileged service principal
|
v
Shared production libraryAuthenticated User
|
v
Client supplies object ID
|
v
Backend trusts object ID
|
X
Ownership check missing
|
v
Privileged service principal
|
v
Shared production libraryThat missing authorization check was the root cause.
27. Remediation
The primary fix is to enforce authorization server-side for every object reference.
Every endpoint accepting:
fileId
driveItemId
entityGUID
folderId
documentIdfileId
driveItemId
entityGUID
folderId
documentIdshould verify that the object belongs to the authenticated user's authorized entity before making the privileged Graph/SharePoint call.
Conceptually:
user_entity = get_authorized_entity(current_user)
obj = get_object(object_id)
if obj.entity_id != user_entity.id:
raise Forbidden()
perform_operation(obj)user_entity = get_authorized_entity(current_user)
obj = get_object(object_id)
if obj.entity_id != user_entity.id:
raise Forbidden()
perform_operation(obj)The exact implementation will depend on the application's data model, but the security property must remain the same:
Authenticated user
β
Authorized entity
β
Requested objectAuthenticated user
β
Authorized entity
β
Requested object28. Additional Recommendations
Protect fallback endpoints
Endpoints such as:
GetRootWhenError
GetChildrenWhenErrorGetRootWhenError
GetChildrenWhenErrorshould either be removed or subjected to the same authorization checks as their normal equivalents.
Don't rely on client-side filtering
React/UI logic can improve usability, but it must never be the authoritative authorization mechanism.
Minimize service-principal permissions
The backend identity should have only the permissions necessary for the operation.
Consider stronger tenant isolation
Where practical, separate vendor data at the storage or permission boundary instead of placing all vendor documents behind one highly privileged service identity.
Audit every object-based endpoint
A single authorization fix may not be enough.
Every endpoint accepting an object identifier should be reviewed.
29. Final Takeaway
This finding reinforced one of the most important principles in API security:
The client can suggest which object it wants. The server must decide whether the user is allowed to access it.
The application appeared to have vendor-level isolation.
The UI understood that users should only interact with their own entity.
But the API trusted client-supplied object identifiers.
That created the critical chain:
Authenticated user
β
No assigned entity
β
Fallback endpoint
β
Shared production root
β
Client-controlled object ID
β
Missing ownership validation
β
Privileged service principal
β
Production SharePoint library
β
Write capability
β
Delete capabilityAuthenticated user
β
No assigned entity
β
Fallback endpoint
β
Shared production root
β
Client-controlled object ID
β
Missing ownership validation
β
Privileged service principal
β
Production SharePoint library
β
Write capability
β
Delete capabilityThe autonomous hunting harness didn't find this by simply scanning for a known BOLA payload.
It found it by connecting several observations:
Object IDs
+
Fallback behavior
+
Authorization boundaries
+
Backend service identity
+
Side effectsObject IDs
+
Fallback behavior
+
Authorization boundaries
+
Backend service identity
+
Side effectsThat is the part of autonomous bug hunting that I find most interesting.
A mature hunting system shouldn't just ask:
"Does this endpoint return 200?"
It should ask:
"What object is being accessed, who owns it, what identity is the backend using, and what happens if I change the object reference?"
And when the answer reveals that a low-privileged authenticated user can cross an object-ownership boundary, the next step isn't necessarily to exploit it further.
The better approach is to prove the capability safely, restore the original state, document the authorization failure, and report the smallest reproducible chain that demonstrates the real impact.
That approach produced a Critical 9.1 BOLA finding with a clean, net-zero proof of concept.