PT-2026-38409 · Pypi · Aegra-Api

Published

2026-05-07

·

Updated

2026-05-07

·

CVE-2026-44504

CVSS v4.0

8.6

High

VectorAV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N

Impact

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:
  • Execute graph runs against User B's thread via POST /threads/{thread id}/runs, POST /threads/{thread id}/runs/stream, or POST /threads/{thread id}/runs/wait
  • Read User B's full checkpoint state via the resulting run's output field
  • Inject arbitrary messages into User B's conversation history (persisted in B's checkpoint)
  • Hide their activity from User B's GET /threads/{thread id}/runs listing because the run carries A's user id
The 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.

Patches

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.

Workarounds

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.
  ...

Root cause

Aegra's authorization model delegates per-resource policy to user-defined @auth.on handlers. When no handler is registered, handle event(...) returns None and the request proceeds (default-allow). Read endpoints in api/threads.py add a defense-in-depth user id filter at the SQL layer, but the run-creation endpoints in api/runs.py skipped that filter. Result: out-of-the-box deployments without custom auth handlers were vulnerable.

Affected endpoints

  • POST /threads/{thread id}/runs
  • POST /threads/{thread id}/runs/stream
  • POST /threads/{thread id}/runs/wait
Stateless variants (POST /runs, POST /runs/wait, POST /runs/stream) are NOT affected — they generate a fresh thread id server-side and never accept a caller-supplied one.

Credits

  • @JoJoTheBizarre — discovered and reported the vulnerability with a precise reproducer (#336)
  • @victorjmarin and @jawhardjebbi — wrote the fix and added test coverage at unit, integration, and manual-auth e2e levels (#337)

Resources

Fix

Improper Authorization

IDOR

Weakness Enumeration

Related Identifiers

CVE-2026-44504
GHSA-M98R-6667-4WQ7

Affected Products

Aegra-Api