# Open WebUI: Stale Admin Role in Socket.IO Session Pool Enables Post-Demotion Cross-User Note Access

Open WebUI caches user role in Socket.IO SESSION_POOL at connect and never refreshes it, so a demoted or deleted admin keeps admin access to any user's Yjs notes.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: Classic298
- Disclosed: 2026-05-08
- CVE: CVE-2026-44553
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-45m8-cpm2-3v65
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-45m8-cpm2-3v65
- Tags: area:web domain:web impact:data-leak impact:integrity kind:research platform:ghsa sev:high tech:websocket technique:source-review vuln:access-control vuln:auth-bypass vuln:privilege-escalation

**Target:** Socket.IO session pool and Yjs collaborative document handlers
**Root cause:** The connect handler snapshots user.role into SESSION_POOL and heartbeat only refreshes last_seen_at; role update and user delete in routers/users.py never invalidate SESSION_POOL, while ydoc handlers authorize against the cached role instead of the DB.
**Steps:**
1. Log in as admin, establish a live Socket.IO connection so SESSION_POOL[sid] stores role='admin'.
2. Admin A demotes the user via `POST /api/v1/users/{B_id}/update`; DB role becomes 'user' but no disconnect or SESSION_POOL update occurs.
3. Keep the socket alive with periodic `heartbeat` events, which only refresh last_seen_at.
4. Emit `ydoc:document:join` with `document_id = 'note:<victim_note_id>'`; line 538 check `user.get('role') != 'admin'` passes on stale role, joining the room and receiving full document state.
5. Emit `ydoc:document:update` for the same note; line 611 cached-admin check bypasses authz and persists content via `Notes.update_note_by_id`.
**How found:** Code review of the Socket.IO handlers: noticed role is snapshotted at connect, heartbeat never refreshes it, ydoc handlers read cached role, and role-update/delete paths don't touch SESSION_POOL — while HTTP get_current_user refetches from DB, isolating the gap to the socket cache.
**Exploitation:** Attacker holds an admin Socket.IO session, gets demoted or deleted, keeps the connection alive via heartbeats, then joins and writes to arbitrary victims' Yjs note documents using the stale cached admin role.
**Escalation:** Same bypass applies after full user deletion (delete_user_by_id), and heartbeats extend the stale privilege indefinitely.
**Insight:** When a realtime layer caches identity/role at handshake, diff its authorization callsites against the HTTP path that refetches from DB; then check whether the admin mutation endpoints (role change, delete) iterate the session pool to evict — they usually don't.

