From 22f2fe1ffb66c993dad1e0b2b35514acaed2370e Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Wed, 17 Jun 2026 00:06:13 +0200 Subject: [PATCH] Require auth on ydoc:awareness:update and ydoc:document:leave handlers (CWE-306) (#25946) These were the only two ydoc Socket.IO handlers missing the SESSION_POOL guard that every sibling (join/state/update) enforces. With always_connect=True admitting unauthenticated sockets, a client that knew a note's document_id could broadcast spoofed awareness (cursors/presence) and fake ydoc:user:left events into a live editing room, attributed to any client-supplied user_id. Both handlers now require an authenticated SESSION_POOL session; the awareness handler additionally requires prior ydoc:document:join (room membership); and the broadcast user_id is fixed to the authenticated identity instead of the client-supplied value, removing the impersonation. Document content was never reachable (ydoc:document:update already enforces write permission). Co-authored-by: Claude Opus 4.8 (1M context) --- backend/open_webui/socket/main.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 2884847a0e..3333377885 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -757,11 +757,13 @@ async def yjs_document_update(sid, data): @sio.on('ydoc:document:leave') async def yjs_document_leave(sid, data): """Handle user leaving a document""" + user = SESSION_POOL.get(sid) + if not user: # authenticated session required (parity with sibling handlers) + return try: document_id = normalize_document_id(data['document_id']) - user_id = data.get('user_id', sid) - log.info(f'User {user_id} leaving document {document_id}') + log.info(f'User {user["id"]} leaving document {document_id}') # Remove user from the document await YDOC_MANAGER.remove_user(document_id=document_id, user_id=sid) @@ -769,10 +771,10 @@ async def yjs_document_leave(sid, data): # Leave Socket.IO room await sio.leave_room(sid, f'doc_{document_id}') - # Notify other users + # Notify other users; user_id is the authenticated identity, not client-supplied await sio.emit( 'ydoc:user:left', - {'document_id': document_id, 'user_id': user_id}, + {'document_id': document_id, 'user_id': user['id']}, room=f'doc_{document_id}', ) @@ -787,16 +789,21 @@ async def yjs_document_leave(sid, data): @sio.on('ydoc:awareness:update') async def yjs_awareness_update(sid, data): """Handle awareness updates (cursors, selections, etc.)""" + user = SESSION_POOL.get(sid) + if not user: # authenticated session required (parity with sibling handlers) + return try: - document_id = data['document_id'] - user_id = data.get('user_id', sid) + document_id = normalize_document_id(data['document_id']) + room = f'doc_{document_id}' + if room not in sio.rooms(sid): # must have joined the document first + return update = data['update'] - # Broadcast awareness update to all other users in the document + # Broadcast to the room; user_id is the authenticated identity, not client-supplied await sio.emit( 'ydoc:awareness:update', - {'document_id': document_id, 'user_id': user_id, 'update': update}, - room=f'doc_{document_id}', + {'document_id': document_id, 'user_id': user['id'], 'update': update}, + room=room, skip_sid=sid, )