{"id":"PYSEC-2026-2328","summary":"Aegra has cross-user run injection in /threads/{thread_id}/runs (IDOR)","details":"## Impact\n\nAegra 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:\n\n- 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`\n- Read User B's full checkpoint state via the resulting run's `output` field\n- Inject arbitrary messages into User B's conversation history (persisted in B's checkpoint)\n- Hide their activity from User B's `GET /threads/{thread_id}/runs` listing because the run carries A's `user_id`\n\nThe streaming variant is worse — the first SSE `event: values` frame returns the entire prior `messages` array immediately on connection, no graph execution needed.\n\nThread IDs are UUIDs but leak through frontend URLs, server logs, observability traces, and shared links. Guessing is not required.\n\n## Patches\n\nFixed 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.\n\n## Workarounds\n\nIf 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.\n\nExample mitigation handler:\n\n```python\nfrom langgraph_sdk import Auth\n\nauth = Auth()\n\n@auth.on(\"threads\", \"create_run\")\nasync def enforce_thread_owner(ctx: Auth.types.AuthContext, value: dict):\n    # Look up the thread, raise 404 if not owned by ctx.user.identity.\n    # Implementation depends on your data layer.\n    ...\n```\n\n## Root cause\n\nAegra'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.\n\n## Affected endpoints\n\n- `POST /threads/{thread_id}/runs`\n- `POST /threads/{thread_id}/runs/stream`\n- `POST /threads/{thread_id}/runs/wait`\n\nStateless 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.\n\n## Credits\n\n- @JoJoTheBizarre — discovered and reported the vulnerability with a precise reproducer (#336)\n- @victorjmarin and @jawhardjebbi — wrote the fix and added test coverage at unit, integration, and manual-auth e2e levels (#337)\n\n## Resources\n\n- Issue: https://github.com/aegra/aegra/issues/336\n- Fix PR: https://github.com/aegra/aegra/pull/337\n- Release: https://github.com/aegra/aegra/releases/tag/v0.9.7","aliases":["CVE-2026-44504","GHSA-m98r-6667-4wq7"],"modified":"2026-07-13T16:31:30.415106896Z","published":"2026-07-13T15:15:40.572514Z","references":[{"type":"WEB","url":"https://github.com/aegra/aegra/security/advisories/GHSA-m98r-6667-4wq7"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-44504"},{"type":"WEB","url":"https://github.com/aegra/aegra/issues/336"},{"type":"WEB","url":"https://github.com/aegra/aegra/pull/337"},{"type":"WEB","url":"https://github.com/aegra/aegra/commit/e1b2042254fd49072ca281bc35b3f2a3bed74b31"},{"type":"PACKAGE","url":"https://github.com/aegra/aegra"},{"type":"WEB","url":"https://github.com/aegra/aegra/releases/tag/v0.9.7"},{"type":"PACKAGE","url":"https://pypi.org/project/aegra-api"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-m98r-6667-4wq7"}],"affected":[{"package":{"name":"aegra-api","ecosystem":"PyPI","purl":"pkg:pypi/aegra-api"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.9.0"},{"fixed":"0.9.7"}]}],"versions":["0.9.0","0.9.1","0.9.2","0.9.3","0.9.4","0.9.5","0.9.6"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/aegra-api/PYSEC-2026-2328.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V4","score":"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"}]}