high · 7.7CVE-2026-59216Jul 24, 2026

CVE-2026-59216: Open WebUI Cross-User Code Execution via Unvalidated Socket.IO session_id

Rohit Hatagale
AI Security Researcher, SecureLayer7

A logged-in user can hijack another user's browser session and run arbitrary code inside it, including as an administrator, by substituting the victim's Socket.IO session ID into a normal chat…

Packageopen-webui
Ecosystempip
Affected< 0.10.0
Fixed in0.10.0
CVE-2026-59216: Open WebUI Cross-User Code Execution via Unvalidated Socket.IO session_id

The problem

In Open WebUI versions before 0.10.0, the get_event_call function in backend/open_webui/socket/main.py routes execute:python and execute:tool Socket.IO events to whichever session_id the client sends in the request body. The only check is whether that session ID exists in SESSION_POOL (i.e., is connected), not whether it belongs to the requesting user.

A separate weakness in ydoc:document:join leaks the live socket IDs of every participant in a shared collaborative note to any read-access member of that note. Combined, these two issues let a low-privilege attacker learn a victim's socket ID and then fire code-interpreter or tool-execution events directly into that victim's browser session.

When the victim is an administrator, the hijacked session reaches the Functions API whose source is exec()'d server-side, giving the attacker remote code execution as root in the default container.

Proof of concept

A working proof-of-concept for CVE-2026-59216 in open-webui, with the exact payload below.

http
# Step 1: attacker shares a note with the victim and captures the victim's session_id
# from the ydoc:document:join room event (Socket.IO, read-access participants).

# Step 2: attacker sends a crafted chat completion carrying the victim's session_id.
# The code-interpreter payload runs in the victim's browser (or, for an admin victim,
# escalates to server-side RCE via the Functions API).

POST /api/v1/chat/completions HTTP/1.1
Host: <target>
Authorization: Bearer <attacker_token>
Content-Type: application/json

{
  "model": "<attacker_controlled_model_or_direct_connection>",
  "session_id": "<victim_socket_id_from_ydoc_room>",
  "messages": [
    {"role": "user", "content": "run my payload"}
  ],
  "tool_ids": ["<attacker_tool_id>"]
}

# The backend passes session_id straight into get_event_call, which checks only:
#   if session_id not in SESSION_POOL: return error   # victim IS connected
# and then emits execute:tool / execute:python to the victim's socket ID.
# No ownership check is performed before 0.10.0.

The root cause is CWE-639: Authorization Bypass Through User-Controlled Key. The session_id field is taken directly from the attacker's request body (form_data.pop('session_id', None)) and placed into metadata alongside the server-derived user_id. Inside get_event_call, the code checked only session_id not in SESSION_POOL, which is satisfied whenever the victim is online, never confirming that SESSION_POOL[session_id]['id'] == request_info['user_id'].

The patch (commit 386ac958, PR #25763) adds exactly that comparison: session is None or session.get('id') != request_info.get('user_id'), using the server-derived user_id as the authority. Because user_id comes from the authenticated JWT and not from the request body, an attacker cannot forge it.

The advisory also recommends restricting ydoc:document:join to stop leaking socket IDs as defence-in-depth, but the ownership check alone closes the cross-user delivery primitive.

The fix

Upgrade to Open WebUI 0.10.0. The fix in commit 386ac958 (PR #25763) adds an ownership check to get_event_call: the target session must belong to the requesting user (session.get('id') == request_info.get('user_id')) before any execute:python or execute:tool event is dispatched.

As additional hardening, restrict or audit ydoc:document:join to prevent socket ID disclosure to room participants.

Reported by doge-woof.

References: [1][2][3][4][5][6]

Related research