Skip to content
Open WebUIGHSA-8788-j68r-3cgh

Open WebUI: Any authenticated user can read other users' private notes via Socket.IO

Medium5.3CVE-2026-54022 · Published Jun 17, 2026 · updated Jul 20, 2026

### Summary The `ydoc:document:join` Socket.IO handler checks note ownership only when the `document_id` starts with `note:` (colon). However, the `YdocManager` storage layer normalizes all document IDs by replacing colons with underscores (`document_id.replace(":", "_")`). An attacker can join a document room using `note_<id>` (underscore) instead of `note:<id>` (colon), bypassing the authorization check entirely while accessing the same underlying Yjs document. The server then returns the full document state, leaking the victim's private note contents. ### Details The `ydoc:document:join` handler in `socket/main.py` (line 511) only performs authorization for document IDs matching the `note:` prefix: ```python @sio.on("ydoc:document:join") async def ydoc_document_join(sid, data): document_id = data["document_id"] if document_id.startswith("note:"): note_id = document_id.split(":")[1] note = Notes.get_note_by_id(note_id) # ... ownership and AccessGrants check ... # Returns early if user doesn't have access # If document_id does NOT start with "note:", execution continues # with no authorization check at all await YDOC_MANAG...

GitHub advisory

Affected versions

PackageAffectedFixed in
open-webui
PyPI
< 0.8.110.8.11
Details and references

### Summary The `ydoc:document:join` Socket.IO handler checks note ownership only when the `document_id` starts with `note:` (colon). However, the `YdocManager` storage layer normalizes all document IDs by replacing colons with underscores (`document_id.replace(":", "_")`). An attacker can join a document room using `note_<id>` (underscore) instead of `note:<id>` (colon), bypassing the authorization check entirely while accessing the same underlying Yjs document. The server then returns the full document state, leaking the victim's private note contents. ### Details The `ydoc:document:join` handler in `socket/main.py` (line 511) only performs authorization for document IDs matching the `note:` prefix: ```python @sio.on("ydoc:document:join") async def ydoc_document_join(sid, data): document_id = data["document_id"] if document_id.startswith("note:"): note_id = document_id.split(":")[1] note = Notes.get_note_by_id(note_id) # ... ownership and AccessGrants check ... # Returns early if user doesn't have access # If document_id does NOT start with "note:", execution continues # with no authorization check at all await YDOC_MANAGER.add_user(document_id=document_id, user_id=sid) await sio.enter_room(sid, f"doc_{document_id}") ydoc = Y.Doc() updates = await YDOC_MANAGER.get_updates(document_id) for update in updates: ydoc.apply_update(bytes(update)) state_update = ydoc.get_update() await sio.emit("ydoc:document:state", { "document_id": document_id, "state": list(state_update), }, room=sid) ``` The `YdocManager` class in `socket/utils.py` normalizes document IDs in every method by replacing colons with underscores: ```python async def get_updates(self, document_id: str) -> List[bytes]: document_id = document_id.replace(":", "_") # line 176 # ... returns updates keyed by normalized ID async def append_to_updates(self, document_id: str, update: bytes): document_id = document_id.replace(":", "_") # line 134 # ... stores update keyed by normalized ID ``` This means `note:abc123` and `note_abc123` resolve to the same storage key (`note_abc123`). When a victim opens their note, the Yjs document is stored under the normalized key. An attacker can then request the same document using the underscore variant, which skips the `startswith("note:")` authorization check but retrieves the same data from `YdocManager`. ### PoC ```python #!/usr/bin/env python3 """ uv run --no-project --with requests --with "python-socketio[asyncio_client]" --with aiohttp --with pycrdt finding_15_yjs_note_disclosure.py --base-url BASE_URL --attacker-email EMAIL --attacker-password PASS --victim-email EMAIL --victim-password PASS Finding #15 , Any authenticated user can read other users' private notes via Socket.IO SUMMARY: The ydoc:document:join Socket.IO handler only checks authorization for document IDs starting with "note:" (colon). However, YdocManager normalizes document IDs by replacing colons with underscores internally. An attacker can join a room using "note_<id>" (underscore) to bypass the auth check, while still accessing the same underlying Yjs document as "note:<id>". Then ydoc:document:state returns the full document content. VULNERABLE CODE: backend/open_webui/socket/main.py, ydoc:document:join: if document_id.startswith("note:"): # permission check only for colon-prefix # "note_<id>" skips this check entirely backend/open_webui/socket/ydoc.py, YdocManager: key = document_id.replace(":", "_") # normalizes to same storage key IMPACT: Any authenticated user can read the full content of any other user's notes by exploiting the namespace collision between "note:" and "note_" prefixes. REPRODUCTION: 1. Victim creates a private note with sensitive content. 2. Attacker connects via Socket.IO and authenticates. 3. Attacker joins room with document_id "note_<victim_note_id>" (

CVSS 3.1
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
Severity from
GitHub (reviewed advisory)
Weakness
CWE-706, CWE-863
Also known as
CVE-2026-54022, PYSEC-2026-2712

More Open WebUI advisories

All Open WebUI

Critical advisories by email

Wednesdays: the week’s critical and high advisories in the AI and data stack, with the fixed versions. Only in weeks that have some.

Double opt-in. Unsubscribe any time.