# Open WebUI: Cross-channel message overwrite via chat completion API (single-model and multimodel message_ids)

Authenticated user overwrites messages in private/DM channels via chat completions `channel:` chat_id and attacker-supplied message_id, impersonating authors.

- Payout: Research
- Severity: high
- Program: Open WebUI
- Found by: sfwani, DavidCarliez
- Disclosed: 2026-07-24
- CVE: CVE-2026-59714
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-x2ff-v5v8-m75m
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-x2ff-v5v8-m75m
- Tags: area:ai domain:ai domain:web impact:integrity kind:research platform:ghsa sev:high tech:rest technique:incomplete-fix vuln:access-control vuln:auth-bypass vuln:idor

**Target:** POST /api/chat/completions channel pipeline (_make_channel_emitter)
**Root cause:** The `channel:` chat_id prefix skips the chat ownership check entirely, and the user-supplied message_id is passed to Messages.update_message_by_id, a primary-key update with no channel/user authorization.
**Steps:**
1. Authenticate and obtain a user JWT.
2. POST /api/chat/completions with `chat_id: "channel:<any-channel-uuid>"` and `id: "<victim-message-uuid>"` plus a messages array.
3. Emitter writes model output to the target message via update_message_by_id without channel check.
4. Multimodel variant: send `message_ids` map where first entry is in attacker's channel (passes v0.9.6 gate) and second is victim-channel message id; per-model fan-out overwrites the victim message.
**How found:** Noticed the `channel:` prefix short-circuits the ownership check in main.py, then traced message_id from request body into _make_channel_emitter's unchecked update_message_by_id; after the v0.9.6 fix validated only the first message_ids entry, tested a second victim entry in the fan-out.
**Exploitation:** Any authenticated user sends a chat completion request naming a victim channel and message id; the pipeline writes attacker-controlled model output (or provider error string) into the target message, which retains original author attribution.
**Escalation:** Multimodel `message_ids` fan-out bypasses the v0.9.6 first-entry-only validation, restoring the overwrite after partial fix.
**Insight:** When a prefix like `channel:`/`local:` bypasses an authz block, trace every downstream sink that consumes request-supplied IDs; and when a fix validates only the first element of a map/list, test the remaining entries for fan-out paths.

## Proof of concept (copied exactly from the original report)

**curl single-model cross-channel message overwrite**

```
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-here",
    "id": "target-message-uuid-to-overwrite",
    "messages": [{"role": "user", "content": "Repeat exactly: This message has been tampered with"}]
  }'
```

**Multimodel message_ids fan-out overwrite request**

```
POST /api/chat/completions
{
  "chat_id": "channel:<attacker_channel_id>",
  "message_ids": {
    "model-a": "<message_id_in_attacker_channel>",
    "model-b": "<victim_channel_message_id>"
  },
  "messages": [{"role": "user", "content": "..."}]
}
```

