Proof-of-concept demonstration for CVE-2026-30950, an authenticated IDOR (Missing Authorization) in the AutoGPT Platform that lets any logged-in user reassign — and thus hijack — any other user's chat session via a single PATCH request, with no prior access to the session.
- GHSA: GHSA-q58p-v9r9-7gqj
- CVE: CVE-2026-30950
- CVSS 3.1: 7.1 / High (
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:L) - Affected:
autogpt-platform-backend >= 0.6.36 - Fixed in: 0.6.51
- Discovered by ZeroPath. Our technical blog contains a full write up with more details!
The chat-session API exposes a route that lets a user attach their own
account to a session record. The route is gated by JWT authentication
but performs no check that the caller currently owns the session
being modified — only that they are some authenticated user. A direct
call with the victim's session_id and the attacker's JWT transfers
ownership of the session to the attacker.
The vulnerability has three layers and the bypass is established at each one:
# autogpt_platform/backend/backend/api/features/chat/routes.py:753-776
@router.patch(
"/sessions/{session_id}/assign-user",
dependencies=[Security(auth.requires_user)],
status_code=200,
)
async def session_assign_user(
session_id: str,
user_id: Annotated[str, Security(auth.get_user_id)],
) -> dict:
await chat_service.assign_user_to_session(session_id, user_id)
return {"status": "ok"}The route accepts any authenticated caller. It hands off to the service:
# autogpt_platform/backend/backend/copilot/service.py:291-303
async def assign_user_to_session(session_id: str, user_id: str) -> ChatSessionInfo:
session = await get_chat_session(session_id, None) # ← user_id=None
if not session:
raise NotFoundError(f"Session {session_id} not found")
session.user_id = user_id
session = await upsert_chat_session(session)
return sessionThe service deliberately passes user_id=None to the data accessor,
instead of forwarding the caller's user ID. The data accessor then
treats None as admin-mode and skips the ownership filter:
# autogpt_platform/backend/backend/copilot/model.py:355-366
session = await _get_session_from_cache(session_id)
if session:
if user_id is not None and session.user_id != user_id:
logger.warning(f"Session {session_id} user id mismatch")
return None
return sessionWhen user_id is None, the conjunction short-circuits and the mismatch
check is never performed — any session is returned to any caller. The
service then overwrites session.user_id with the caller's ID and
caches the result in Redis, so subsequent lookups by the original owner
are rejected by the same mismatch check that was just bypassed.
Any AutoGPT Platform instance running autogpt-platform-backend
>= 0.6.36 and < 0.6.51 with the chat (copilot) feature enabled
is exploitable. The exploit requires:
- An authenticated session. The attacker must hold any valid Supabase JWT — a standard signed-up user account is sufficient.
- A target session ID. Session IDs are UUIDs but they appear in URLs and logs; any leak — referer headers, shared links, screen shares, server logs, support tickets — is enough.
Per-session impact, escalating with each hijacked session:
- Read all messages in the hijacked session. Chat sessions in AutoGPT contain conversation history with the agent, including tool invocations, file references, and any sensitive data the user pasted into the chat.
- Lock the legitimate owner out. After the assignment, the Redis cache holds the attacker's user_id; the victim's subsequent reads fail the ownership check and return 404.
- Pivot through the session. Whatever the session was authorized to do for the victim, the attacker can now do — submit follow-up messages, trigger agent actions, exfiltrate workspace contents attached to the session.
The CVSS score (C:H / I:N / A:L) reflects the per-session scope: high confidentiality impact on hijacked sessions, no integrity damage to pre-existing message contents, and an availability impact (lockout) on the legitimate owner.
This repository ships one POC, covering the direct trigger: a
single PATCH request from an attacker's JWT against a known
session_id. The setup script creates two users (attacker + victim)
in a freshly-bootstrapped AutoGPT stack and verifies the preconditions
before declaring ready.
-
setup/— Docker Compose environment.setup.shclones AutoGPT at the last vulnerable tag (autogpt-platform-beta-v0.6.50) intosetup/AutoGPT-src/, brings up the minimum services from upstream'sdocker-compose.yml(rest_server,copilot_executor,database_manager,migrate, plus their transitive deps: Postgres, Redis, RabbitMQ, Supabase Kong + GoTrue), creates two test users, and verifies the vulnerable endpoint is routed.teardown.shbrings down the stack and wipes volumes.
-
pocs/session_hijack.py— Self-contained exploit. Logs in as victim and attacker, creates a session as the victim, confirms the attacker has no pre-exploit read access, fires the single PATCH request, then proves ownership has transferred and the victim is locked out.
Prerequisites: Docker, Git, Python 3.10+, and uv.
cd setup
./setup.shThe first run clones AutoGPT at the vulnerable tag and builds the backend image (~3-5 min). Subsequent runs are quick. When setup completes it prints the ready-to-paste POC invocation.
Run the POC:
uv run --no-project --with requests \
pocs/session_hijack.py \
--api-url http://localhost:58006 \
--auth-url http://localhost:58000 \
--attacker-email attacker@autogpt-poc.local \
--attacker-password 'Attacker123!' \
--victim-email victim@autogpt-poc.local \
--victim-password 'Victim123!'Why the high ports? Upstream's compose hardcodes container names (
supabase-db,rabbitmq, ...) and publishes default ports (5432, 8000, ...). To coexist with any other Supabase / Postgres / RabbitMQ stacks the user might already be running,setup/docker-compose.override.ymlrenames the containers, isolates the networks under a unique project name (autogpt-cve-2026-30950), and bumps the two externally-exposed ports into the 58000 range. The other internal services bind only to the project's docker network.
Expected output ends with:
[PASS] AUTHENTICATED SESSION HIJACK CONFIRMED
Tear down:
cd setup
./teardown.shThe cloned AutoGPT source in setup/AutoGPT-src/ is preserved across
teardowns so re-runs don't re-download. To wipe it entirely, delete
setup/AutoGPT-src/.