CVE-2026-44504: Aegra: cross-tenant IDOR hijacks user thread data

GHSA-m98r-6667-4wq7 HIGH CISA: TRACK*
Published May 7, 2026
CISO Take

Any authenticated user in a shared Aegra deployment (versions 0.9.0–0.9.6) can read, execute against, and inject messages into any other user's AI conversation thread by simply knowing their thread_id — a UUID that routinely leaks through application URLs, server logs, and observability traces, requiring no brute-force. With 3,071 downstream dependents and a default-allow authorization model that skips auth checks entirely when no custom handler is registered, the blast radius for SaaS platforms built on Aegra is significant; the streaming endpoint is especially dangerous, returning a target's full message history in the first SSE frame with zero graph execution needed. No public exploit or scanner exists yet, but the attack is trivially reproducible for any authenticated tenant with a known thread_id. Patch to aegra-api 0.9.7 immediately; if that's not feasible, register an `@auth.on("threads", "create_run")` handler to enforce ownership checks at the application layer.

Sources: GitHub Advisory ATLAS

What is the risk?

High risk for any multi-tenant Aegra deployment. The root cause is a default-allow authorization model: when no custom `@auth.on` handler is registered, the framework skips authorization entirely on run-creation endpoints while read endpoints had a defense-in-depth SQL filter — an asymmetric security posture that left write paths unguarded. Thread IDs are UUIDs that surface naturally in URLs, logs, traces, and shared links, eliminating the need for enumeration. Exploitation requires only a valid session token. The streaming variant compounds severity by returning complete historical message content in the first SSE frame, enabling exfiltration without triggering graph execution or generating observable compute activity. Although no CVSS score is yet assigned, the combination of trivial exploitability, cross-tenant data exposure, and persistent message injection capability warrants HIGH classification.

How does the attack unfold?

Initial Access
Attacker registers a valid account (trial or paid) on a shared Aegra-backed platform, obtaining a legitimate session token.
AML.T0012
Target Discovery
Attacker obtains a victim's thread_id from application URLs, frontend navigation, server logs, observability traces, or shared links — no enumeration or brute-force required.
AML.T0003
Exploitation
Attacker POSTs to /threads/{victim_thread_id}/runs/stream with their valid Bearer token; no authorization check fires in the default deployment, and the first SSE frame returns the victim's full conversation history.
AML.T0049
Impact
Attacker exfiltrates the victim's complete AI conversation history and optionally injects persistent messages to poison the victim's future AI interactions, with the activity hidden from the victim's run listing.
AML.T0080.001

What systems are affected?

Package Ecosystem Vulnerable Range Patched
LangGraph pip >= 0.9.0, < 0.9.7 0.9.7
35.3K 3.3K dependents Pushed 4d ago 80% patched ~3d to patch Full package profile →

Do you use LangGraph? You're affected.

How severe is it?

CVSS 3.1
N/A
EPSS
0.3%
chance of exploitation in 30 days
Higher than 20% of all CVEs
Exploitation Status
No known exploitation
Sophistication
Trivial

What should I do?

5 steps
  1. Patch: Upgrade aegra-api to 0.9.7 — the fix adds SQL-level user_id ownership checks to POST /threads/{thread_id}/runs, /runs/stream, and /runs/wait before _prepare_run is called, returning 404 for threads owned by other users.

  2. Workaround (if upgrade is not immediately possible): Implement an @auth.on('threads', 'create_run') handler that validates ctx.user.identity against the thread's owner_id before permitting the operation — without this handler, no built-in authorization runs.

  3. Detection: Query the runs table directly for cross-user ownership mismatches (run.user_id != thread.user_id) to identify exploitation attempts; the vulnerability suppresses these anomalies from the victim's own run listing.

  4. Review SSE access logs for thread_id access patterns inconsistent with thread ownership.

  5. If cross-tenant exposure is confirmed, notify affected users and consider thread_id rotation or invalidation.

What does CISA's SSVC say?

Decision Track*
Exploitation none
Automatable Yes
Technical Impact total

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

How is it classified?

Which compliance frameworks are affected?

This CVE is relevant to:

EU AI Act
Art. 10 - Data and data governance Art. 9 - Risk management system
ISO 42001
A.6.1.3 - Information security roles and responsibilities for AI systems A.8.4 - Access control for AI system resources
NIST AI RMF
GOVERN 1.7 - Processes and procedures for AI risk management MANAGE 2.2 - Mechanisms for AI risk response and recovery
OWASP LLM Top 10
LLM06 - Sensitive Information Disclosure

Frequently Asked Questions

What is CVE-2026-44504?

Any authenticated user in a shared Aegra deployment (versions 0.9.0–0.9.6) can read, execute against, and inject messages into any other user's AI conversation thread by simply knowing their thread_id — a UUID that routinely leaks through application URLs, server logs, and observability traces, requiring no brute-force. With 3,071 downstream dependents and a default-allow authorization model that skips auth checks entirely when no custom handler is registered, the blast radius for SaaS platforms built on Aegra is significant; the streaming endpoint is especially dangerous, returning a target's full message history in the first SSE frame with zero graph execution needed. No public exploit or scanner exists yet, but the attack is trivially reproducible for any authenticated tenant with a known thread_id. Patch to aegra-api 0.9.7 immediately; if that's not feasible, register an `@auth.on("threads", "create_run")` handler to enforce ownership checks at the application layer.

Is CVE-2026-44504 actively exploited?

No confirmed active exploitation of CVE-2026-44504 has been reported, but organizations should still patch proactively.

How to fix CVE-2026-44504?

1. Patch: Upgrade aegra-api to 0.9.7 — the fix adds SQL-level user_id ownership checks to POST /threads/{thread_id}/runs, /runs/stream, and /runs/wait before _prepare_run is called, returning 404 for threads owned by other users. 2. Workaround (if upgrade is not immediately possible): Implement an `@auth.on('threads', 'create_run')` handler that validates ctx.user.identity against the thread's owner_id before permitting the operation — without this handler, no built-in authorization runs. 3. Detection: Query the runs table directly for cross-user ownership mismatches (run.user_id != thread.user_id) to identify exploitation attempts; the vulnerability suppresses these anomalies from the victim's own run listing. 4. Review SSE access logs for thread_id access patterns inconsistent with thread ownership. 5. If cross-tenant exposure is confirmed, notify affected users and consider thread_id rotation or invalidation.

What systems are affected by CVE-2026-44504?

This vulnerability affects the following AI/ML architecture patterns: agent frameworks, multi-tenant LLM applications, LLM conversation and thread management systems, AI agent orchestration platforms.

What is the CVSS score for CVE-2026-44504?

No CVSS score has been assigned yet.

What is the AI security impact?

Affected AI Architectures

agent frameworksmulti-tenant LLM applicationsLLM conversation and thread management systemsAI agent orchestration platforms

MITRE ATLAS Techniques

AML.T0012 Valid Accounts
AML.T0049 Exploit Public-Facing Application
AML.T0057 LLM Data Leakage
AML.T0080.001 Thread
AML.T0085 Data from AI Services

Compliance Controls Affected

EU AI Act: Art. 10, Art. 9
ISO 42001: A.6.1.3, A.8.4
NIST AI RMF: GOVERN 1.7, MANAGE 2.2
OWASP LLM Top 10: LLM06

What are the technical details?

Original Advisory

## 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: ```python 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 - Issue: https://github.com/aegra/aegra/issues/336 - Fix PR: https://github.com/aegra/aegra/pull/337 - Release: https://github.com/aegra/aegra/releases/tag/v0.9.7

Exploitation Scenario

An attacker with a legitimate trial account on an Aegra-backed SaaS platform observes a colleague's or target's thread_id in a shared dashboard URL, support ticket, or browser history. They issue a POST to /threads/{victim_thread_id}/runs/stream with their own valid Bearer token. No auth check fires because no `@auth.on` handler is registered in the default deployment. The first SSE `event: values` frame immediately returns the victim's entire prior messages array — including all LLM inputs, tool call arguments and outputs, and system context — without any graph execution occurring. The attacker then executes a crafted graph run to inject a persistent message into the victim's thread, poisoning the AI context for all future interactions. The entire operation leaves run records attributed to the attacker's user_id, invisible in the victim's own GET /threads/{thread_id}/runs listing.

Weaknesses (CWE)

CWE-285 — Improper Authorization: The product does not perform or incorrectly performs an authorization check when an actor attempts to access a resource or perform an action.

  • [Architecture and Design] Divide the product into anonymous, normal, privileged, and administrative areas. Reduce the attack surface by carefully mapping roles with data and functionality. Use role-based access control (RBAC) to enforce the roles at the appropriate boundaries. Note that this approach may not protect against horizontal authorization, i.e., it will not protect a user from attacking others with the same role.
  • [Architecture and Design] Ensure that you perform access control checks related to your business logic. These checks may be different than the access control checks that you apply to more generic resources such as files, connections, processes, memory, and database records. For example, a database may restrict access for medical records to a specific database user, but each record might only be intended to be accessible to the patient and the patient's doctor.

Source: MITRE CWE corpus.

Timeline

Published
May 7, 2026
Last Modified
May 7, 2026
First Seen
May 7, 2026

Related Vulnerabilities