Our Red Team AI Division (RAID) has yet another major finding…
Sometimes the most dangerous vulnerabilities aren’t hiding behind complex bypass chains or novel attack techniques. Sometimes they’re sitting in plain sight, protected only by the assumption that nobody would bother to look at how the frontend actually talks to the backend. Our agentic web testing system proved that assumption wrong during a recent engagement against a retail customer’s multi-site operations platform.
Setting the scene: A modern SPA with an RPC-style API
RAID’s multi-agent system was tasked with a comprehensive assessment of a cloud-hosted operations portal, a React single-page application built with Material UI and Vite, hosted on AWS infrastructure. The application managed scheduling, resource allocation, staff coordination, and user administration across multiple physical locations.
Initial reconnaissance revealed the architecture quickly: a tenant-scoped URL structure (/{clientCode}/section), JWT Bearer tokens stored in localStorage , and a uniform API pattern where virtually every operation was a POST request to /api/{module}/{action}/ with a JSON body.
Two test accounts were provisioned: an admin user and a standard user. The target? Determine whether a standard user could elevate their privileges to admin.
When the documented Endpoint doesn’t work
The Planner agent’s attack plan identified POST /api/member/update/ as the target endpoint for privilege escalation, with a body like {"id": <user_id>, "role": 0}. Straightforward enough. The BAC (Broken Access Control) agent picked up the task and got to work.
Except the endpoint didn’t work. At all.
POST /api/member/update/
HTTP 500
{"error": "Cannot read properties of undefined (reading 'update')"}
The same endpoint with list returned the same class of error:
POST /api/member/list/
HTTP 500
{"error": "Cannot read properties of undefined (reading 'list')"}
This is where a less persistent tool would have reported a dead end and moved on. RAID didn’t.
The pivot: Decompiling the frontend
The agent’s reasoning at this point is instructive. Rather than brute-forcing endpoint variations, it decided to understand how the application actually constructs its API calls:
“The error ‘Cannot read properties of undefined (reading ‘update’)’ suggests the API routes by method name and the body parameters directly influence backend query construction… Let me look at the JS bundle to understand the API structure.”
The agent fetched the main JavaScript bundle, 5.2 MB of minified React code and began surgically extracting relevant patterns:
# Search for update patterns near the module name
grep -oP '.{200}members.update.{200}' /tmp/app.js
This revealed the critical insight. The frontend code showed:
Xe.hooks.members.create()
Xe.hooks.members.update()
Xe.hooks.members.resetPassword()
The module name wasn’t member, it was members. And the API path construction function (Fe) built URLs as:
context.url + "/" + module + "/" + action + "/"
/api/members/update/
One letter. A plural s.
The difference between a 500 error and a working endpoint.
Finding the real role field
With the correct endpoint identified, the agent still needed to know what field controlled the privilege level. The task plan assumed role, but the agent dug deeper:
grep -oP 'Rl=.{200}' /tmp/app.js
This exposed the role enumeration buried in the minified source:
Rl.admin = 0
Rl.standard = 1
Rl.service = 2
Rl.kiosk = 3
Rl.app = 4
Rl.readonly = 5
And the field name? Not role.
It was accessLevel.
The agent confirmed this by examining the update hook context and finding the actual payload structure sent during user modification:
// From the minified bundle's update hook
payload.accessLevel = selectedRole;
payload.primaryLocation = { id: locationId };
payload.locationScope = scopeString;
“The task mentioned /api/member/update/ with {“role”:0} but this endpoint returns HTTP 500. The correct endpoint is /api/members/update/ with {“accessLevel”:0} — discovered via JS bundle analysis showing Fe(context, "members", "update") → /api/members/update/”
Proving the vulnerability
With the correct endpoint /api/members/update/, the correct field accessLevel, and the correct value 0 for admin, the agent executed a methodical proof:
Step 1: Establish the admin baseline
POST /api/members/update/
Authorization: Bearer <admin_token>
Content-Type: application/json
{"id":1435,"accessLevel":0,"firstName":"James","lastName":"W",
"primaryLocation":{"id":1},"locationScope":"|1|","active":true}
HTTP 200, "accessLevel": 0
Admin can set admin role. Expected behaviour. Baseline established.
Step 2: Immediately revert, then test with the standard user
POST /api/members/update/
Authorization: Bearer <standard_user_token>
Content-Type: application/json
{"id":1435,"accessLevel":0,"firstName":"James","lastName":"W",
"primaryLocation":{"id":1},"locationScope":"|1|","active":true}
HTTP 200, "accessLevel": 0
The response confirmed it. The accessLevel field in the response body showed 0 (admin). A standard user had just granted admin privileges to another account with no authorisation check whatsoever.
Going further: Horizontal IDOR
The agent didn’t stop at vertical escalation. It immediately tested whether a standard user could modify any user, not just their own:
POST /api/members/update/
Authorization: Bearer <standard_user_token>
{"id":1822,"accessLevel":1,"firstName":"Sarah","lastName":"K",...}
HTTP 200, "accessLevel": 1
User 1822 was an admin with roles ["platform.supervisor", "platform.admin", "platform.operator"]. The standard user successfully demoted them to a standard account a full horizontal IDOR combined with vertical privilege manipulation.
The endpoint performed no ownership check and no role-based restriction on who could modify whom, or what fields they could change.
Clean revert
True to the rules of engagement, the agent immediately reverted all changes:
User 1435 (James W): Restored to accessLevel=1 (standard)
User 1822 (Sarah K): Restored to accessLevel=0 (admin)
Both users were verified back to their original state before the agent moved on to its next task.
The agent’s reasoning chain
What makes this finding interesting from an AI perspective isn’t the vulnerability itself, missing authorization checks on role modification endpoints are a well-understood bug class. What’s notable is the reasoning chain that led the agent to the correct exploit:
- Endpoint returns 500 – Don’t assume it’s a dead end
- Error message leaks architecture – "Cannot read properties of undefined (reading 'update')" tells us the routing is property-based
- Analyze the source – The frontend JavaScript reveals the real module/action naming
- Field name differs from assumption – discovered via JS enum extraction
- Prove with baseline – Confirm admin can do it, then replay with lower privilege
- Prove breadth – Test horizontal access (different user IDs) not just vertical
- Clean up – Revert all state-changing mutations immediately
RAID is CovertSwarm’s multi-agent AI architecture for continuous security testing. It combines autonomous reconnaissance, specialized attack agents, and human oversight to discover vulnerabilities across customer’s attack surfaces at scale and forms part of CovertSwarm’s Constant Cyber Attack subscription.