A missing object-level authorization check let any authenticated user write into — and read back — another user’s AI assistant conversation by swapping two user-controlled IDs.
An Insecure Direct Object Reference (IDOR) flaw was identified in the Brand Assistant chat feature. An authenticated user could add a message to another user's chat conversation simply by swapping the conversationId and projectId values in a GraphQL request for those belonging to a victim.
The backend authenticated the requester but never verified that they were authorized to act on the referenced conversation and project. Because both identifiers were user-controlled and trusted without an ownership check, the server executed the write against the victim's resources.
A notable secondary consequence: the mutation's response returns the target conversation's message list, so the same request that writes into the victim's chat also reads back the victim's existing messages — adding a confidentiality dimension to an integrity flaw. Rated High.
Each conversation is expected to be scoped to a project (projectId, belonging to a workspace) and a conversation (conversationId, belonging to a user within that project). The security contract: a user may only read or write conversations inside projects they are entitled to. The identifiers are just keys — meaningful only if the server checks that the requesting user is allowed to use them. When that check is missing, the keys become a directory an attacker can walk through. This is the defining characteristic of IDOR / BOLA.
The addBrandAssistantV2Message mutation accepts conversationId, projectId, and message from the client. When invoked, the backend:
The GraphQL selection set requests messages { id role date message } on the returned conversation. Since the server processes the write against the victim's conversation, it serializes that conversation back in the response — turning a write-only IDOR into one with a data-disclosure component.
Documented for defensive validation. Identifiers below are placeholders/redacted.
Step 1 — Authenticate as the attacker with a valid, low-privilege account. Step 2 — Capture a legitimate write in the attacker's own chat via an HTTP proxy; note the attacker's conversationId and projectId. Step 3 — Substitute the victim's identifiers:
POST /api/graphql?on=addBrandAssistantV2Message HTTP/2
Host: <redacted>
Cookie: <ATTACKER_SESSION>
Content-Type: application/json
{
"operationName": "addBrandAssistantV2Message",
"variables": {
"conversationId": "<VICTIM_CONVERSATION_ID>",
"message": "test-injected-message",
"projectId": <VICTIM_PROJECT_ID>
},
"query": "mutation addBrandAssistantV2Message($conversationId: String, $projectId: Int!, $message: String!) { addBrandAssistantV2Message(conversationId: $conversationId, projectId: $projectId, message: $message) { id userId projectId messages { id role date message } } }"
}
Step 4 — Forward. The server appends the attacker's message to the victim's conversation and returns it (with the victim's messages). Step 5 — Confirm. The message now appears in the victim's chat, confirming the missing object-level authorization check.
conversationId belongs to the projectId, nor that both sit inside a workspace the requester can access.Underlying all three is the classic IDOR mistake: authentication was enforced, object-level authorization was not.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:L → ~7.6 (High)
| Metric | Value | Reasoning |
|---|---|---|
| Attack Vector | Network | Exploited over the API. |
| Attack Complexity | Low | A single modified request; requires knowing/guessing target IDs. |
| Privileges Required | Low | Any valid authenticated account. |
| User Interaction | None | No victim action needed. |
| Confidentiality | Low | Victim messages echoed back in the response. |
| Integrity | High | Unauthorized write into another user's conversation. |
| Availability | Low | Minimal availability effect. |
Scoring note: the vector computes to ~7.6 (High); the report cited 8.1, a minor calculation slip. In all interpretations the finding is High.
function addBrandAssistantV2Message(requester, projectId, conversationId, message):
if not userHasAccessToProject(requester, projectId):
return forbidden()
conversation = lookupConversation(conversationId)
if conversation is null: return notFound()
if conversation.projectId != projectId: return forbidden()
if conversation.userId != requester.id and not requester.canAccess(conversation):
return forbidden()
appendMessage(conversation, message)
auditLog(actor=requester, action="addMessage", conversationId, projectId)
return conversation
IDOR/BOLA is the #1 API risk — and invisible to scanners. Farchase tests every object reference against real user permissions.