SESSION_POOL[session_id] is the user record of whoever owns that socket. Because the caller checks only membership (in SESSION_POOL), a request carrying another user's session_id causes execute:python / execute:tool to be delivered to that other user's browser.
Reachability
execute:python / execute:tool are emitted from the code-interpreter and tool-call paths (utils/middleware.py, tools/builtin.py), all routed through get_event_call.
The victim's live session_id is disclosed to any read-access participant of a shared note via ydoc:document:join.
POST /api/v1/chat/completions requires only get_verified_user (the default user role). The attacker uses their own account and a model / Direct Connection they control to choose the payload.
Impact
Any victim: arbitrary code-interpreter Python and tool execution in the victim's authenticated session — the attacker acts with the victim's identity and origin (full session/account compromise).
Admin victim: the hijacked admin context reaches POST /api/v1/functions/create, whose source is exec()'d server-side → remote code execution as the server process (root in the default container).
The Functions API is intended administrator code-execution; the vulnerability here is the cross-user delivery that lets an attacker drive another user's session — including an admin's — into it. The primitive is a full session compromise even against non-admin victims.
Proof of Concept
The reporter's exploit.py reproduced on ghcr.io/open-webui/open-webui:0.9.6 and a build of the v0.9.6 tag, confirming blind server-side RCE out-of-band (callback returns uid=0(root)), using only a low-privilege user account that shared a note with an admin victim. Preconditions: code interpreter enabled; attacker shares a note with the victim; victim opens it while online; admin victim required for server RCE.
Fix
get_event_call must verify the target session belongs to the requesting user before delivering, not merely that it is connected:
session = SESSION_POOL.get(session_id)
if session is None or session.get('id') != request_info.get('user_id'):
return {'error': 'Client session disconnected.'}
user_id in the request metadata is server-derived from the authenticated user, so it is trustworthy. Restricting ydoc:document:join so it does not disclose other participants' socket ids is recommended as defence-in-depth.
Affected / Patched
Affected: < 0.10.0 (last affected release 0.9.6)
Patched: v0.10.0. get_event_call now verifies the target session belongs to the requesting user before delivering (session is None or session.get('id') != request_info.get('user_id')), using the server-derived user_id from the request metadata. The recommended ydoc:document:join sid-disclosure restriction is defence-in-depth and independent of this fix; the ownership check closes the cross-user delivery regardless of whether the victim's sid is known.