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) <noreply@anthropic.com>
This commit is contained in:
Classic298
2026-06-17 00:06:13 +02:00
committed by GitHub
parent 993e749121
commit 22f2fe1ffb

View File

@@ -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,
)