GHSA-m98r-6667-4wq7
Fix: aegra/aegra#337GHSA-m98r-6667-4wq7 is a security vulnerability in aegra-api. O3 Security confirms whether GHSA-m98r-6667-4wq7 is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Aegra has cross-user run injection in /threads/{thread_id}/runs (IDOR)
Real-World Exposure
aegra-apiReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects PyPI packages — download data is not available via public APIs for these ecosystems.
Description
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, orPOST /threads/{thread_id}/runs/wait - Read User B's full checkpoint state via the resulting run's
outputfield - 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}/runslisting because the run carries A'suser_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}/runsPOST /threads/{thread_id}/runs/streamPOST /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
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐍PyPI | aegra-api | ≥ 0.9.0&&< 0.9.7 | 0.9.7 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for aegra-api. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update aegra-api to 0.9.7 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-m98r-6667-4wq7 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-m98r-6667-4wq7 is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-m98r-6667-4wq7. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-m98r-6667-4wq7 in your dependencies?
O3 detects GHSA-m98r-6667-4wq7 across PyPI dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.