Aegra deployments running 0.9.0 through 0.9.6 with multiple authenticated users on a shared instance are vulnerable to a cross-tenant IDOR. Any authenticated user (User A), given another user's thread_id (User B), can:
POST /threads/{thread_id}/runs, POST /threads/{thread_id}/runs/stream, or POST /threads/{thread_id}/runs/waitoutput fieldGET /threads/{thread_id}/runs listing because the run carries A's user_idThe streaming variant is worse — the first SSE event: values frame returns the entire prior messages array immediately on connection, no graph execution needed.
Thread IDs are UUIDs but leak through frontend URLs, server logs, observability traces, and shared links. Guessing is not required.
Fixed in 0.9.7. The three affected endpoints now perform an SQL-level user_id == authenticated_user.identity check before calling _prepare_run. When the thread exists but is owned by another user, the response is 404 Thread not found (matching the read-side pattern) to avoid leaking thread existence.
If upgrade is not immediately possible, register an @auth.on("threads", "create_run") handler that explicitly verifies thread ownership against the authenticated identity before allowing the operation. Without a handler, no built-in authorization runs on these write paths.
Example mitigation handler:
from langgraph_sdk import Auth
auth = Auth()
@auth.on("threads", "create_run")
async def enforce_thread_owner(ctx: Auth.types.AuthContext, value: dict):
# Look up the thread, raise 404 if not owned by ctx.user.identity.
# Implementation depends on your data layer.
...
Aegra's authorization model delegates per-resource policy...
0.9.7Exploitability
AV:NAC:LAT:NPR:LUI:NVulnerable System
VC:HVI:HVA:NSubsequent System
SC:NSI:NSA:N8.6/CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N