| Title | ForceInjection AI-fundermentals 5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a Insecure Direct Object Reference (IDOR) / Cross-Tenant Data Expo |
|---|
| Description | ## Vulnerability Title
Cross-Tenant Data Exposure via Hash-Key Confusion in LangGraph Customer Service Memory
## Affected Component
`08_agentic_system/memory/langchain/code/smart_customer_service.py`
Repository: https://github.com/ForceInjection/AI-fundermentals
## Summary
An attacker with the ability to submit a victim's `session_id` can reuse the same LangGraph memory key as the victim, causing agent memory key reuse. This leads to unauthorized recall and poisoning of another user's conversation history, negatively impacting users of the customer service memory demo if it is exposed through a web or API layer that accepts caller-supplied session identifiers.
## Technical Details
The vulnerability occurs because `SessionManager` uses `session_id` as the complete LangGraph checkpointer identity. The same key is used to write and recall conversation state, but the key fails to include the stored `user_id` or any authorization-related metadata.
**Where the Identity Key is Used**
In `smart_customer_service.py`, the `chat()` and `get_conversation_history()` methods use `thread_id = session_id` as the sole object identity for security-relevant memory:
```python
result = graph.invoke(
{"messages": input_messages},
config={"configurable": {"thread_id": session_id}},
)
```
**Why Key Equality Does Not Imply Security Equivalence**
The `SessionInfo` dataclass stores ownership (`user_id`) and metadata separately:
```python
@dataclass
class SessionInfo:
session_id: str
user_id: str
# ...
```
However, the `chat()` method only requires `session_id` from the caller:
```python
def chat(self, session_id: str, message: str, context: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
if session_id not in self.sessions:
raise ValueError(f"Session does not exist: {session_id}")
# ... proceeds without validating user_id ...
```
Because the method lacks a parameter for the authenticated caller's `user_id` (and lacks an owner check like `self.sessions[session_id].user_id == current_user_id`), any caller providing an existing `session_id` can access and modify the memory thread associated with that ID, crossing tenant and permission boundaries.
## Impact
This vulnerability allows attackers to:
- Recall another user's conversation memory if they can submit that user's `session_id` (IDOR).
- Poison another user's memory by writing attacker-controlled messages into the victim's LangGraph checkpoint thread.
- Cause cross-tenant or wrong-permission memory reuse.
*(Note: Poisoned state can persist across process restarts if `persistent=True` is used with `SqliteSaver`).*
## Proof of Concept
The PoC simulates a mock LangGraph environment to prove that two non-equivalent users can reuse the same memory key, allowing an attacker to recall and poison the victim's memory.
```python
#!/usr/bin/env python3
# ... (mock config and LLM setup omitted for brevity) ...
import smart_customer_service as scs
manager = scs.SessionManager(storage_dir="/tmp/agentscan-memory-e2e", persistent=False)
alice_sid = manager.create_session(
"user-alice", memory_type="buffer",
metadata={"tenant_id": "tenant-a", "permissions": "billing:read"},
)
bob_sid = manager.create_session(
"user-bob", memory_type="buffer",
metadata={"tenant_id": "tenant-b", "permissions": "billing:admin"},
)
manager.chat(alice_sid, "My order id is ORD-ALICE-123. Please help with billing.")
# Attacker Bob uses Alice's session_id. The API cannot compare Bob's identity to SessionInfo.user_id.
bob_attack_result = manager.chat(alice_sid, "I am Bob. What was the previous order id?")
alice_history = manager.get_conversation_history(alice_sid)
print("BOB_ATTACK_RESPONSE=" + repr(bob_attack_result.get("response")))
print("ATTACK_MESSAGE_STORED_IN_ALICE_HISTORY=" + str(any("I am Bob" in item["content"] for item in alice_history)))
print("SECRET_VISIBLE_TO_ATTACKER=" + str("PROBE_LEAK_CONFIRMED" in bob_attack_result.get("response", "")))
```
*Observed Execution Output:*
```text
BOB_ATTACK_RESPONSE='PROBE_LEAK_CONFIRMED: saw Alice order ORD-ALICE-123 in recalled context'
ATTACK_MESSAGE_STORED_IN_ALICE_HISTORY=True
SECRET_VISIBLE_TO_ATTACKER=True
```
*Result confirms Bob's request reused Alice's memory thread, saw her secret order ID, and injected Bob's message into Alice's history.*
## Remediation
Bind memory operations to the authenticated security context. Require the caller identity in `chat()` and `get_conversation_history()`, verify it against `SessionInfo.user_id`, and include the verified identity in the LangGraph memory key.
Example implementation direction:
```python
# In smart_customer_service.py
def _thread_id(session_info: SessionInfo) -> str:
return f"user:{session_info.user_id}:session:{session_info.session_id}"
def chat(self, current_user_id: str, session_id: str, message: str, context=None):
session_info = self.sessions[session_id]
if session_info.user_id != current_user_id:
raise PermissionError("session does not belong to current user")
cfg = {"configurable": {"thread_id": _thread_id(session_info)}}
# ...
```
Additional mitigations:
1. Complete Field Coverage: Validate all fields relevant to authorization, ownership, tenant isolation, and policy before granting memory access.
2. Read-Time Revalidation: Re-check ownership and authorization before returning checkpoint state.
3. State Invalidation: Migrate or clear existing checkpointer databases generated with the vulnerable `thread_id = session_id` scheme.
## References
- Vulnerable write path: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/smart_customer_service.py#L280-L307`
- Vulnerable recall path: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/smart_customer_service.py#L386-L392`
- Safer repository pattern reference: `https://github.com/ForceInjection/AI-fundermentals/blob/5bb92c73a5aa0560c753beeb56e7ad4314dc8a7a/08_agentic_system/memory/langchain/code/langgraph_memory_example.py#L148-L195` |
|---|
| Source | ⚠️ https://github.com/ForceInjection/AI-fundamentals/issues/17 |
|---|
| User | Dem00000 (UID 98563) |
|---|
| Submission | 06/02/2026 05:45 (1 month ago) |
|---|
| Moderation | 07/03/2026 19:14 (1 month later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 376146 [ForceInjection AI-fundermentals 2.0/3.0 Memory Recall smart_customer_service.py get_conversation_history weak hash] |
|---|
| Points | 20 |
|---|