From f8782dd46abf105a2a6d1dbb5ea113620bb3f699 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:44:40 +0200 Subject: [PATCH] fix: scope every channel chat-completion message_id to the channel, not just the first #24725 added a channel-access + message-scoping gate to the chat_completion `channel:` branch, but it validated only list(message_ids.values())[0]. A multimodel request (message_ids with more than one entry) could pass a permitted-channel id as the first value and a victim-channel message id as a later value; the fan-out then drove _make_channel_emitter to overwrite the victim message in a channel the caller cannot access. Validate every message_ids value against the channel, and make _make_channel_emitter fail closed so it only updates a message whose channel_id matches the channel in chat_id. Co-authored-by: DavidCarliez <271374756+DavidCarliez@users.noreply.github.com> Co-Authored-By: Claude Opus 4.8 --- backend/open_webui/main.py | 6 ++++-- backend/open_webui/socket/main.py | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index e05497c616..a23d2c8a02 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -1834,8 +1834,10 @@ async def chat_completion( status_code=status.HTTP_403_FORBIDDEN, detail=ERROR_MESSAGES.DEFAULT(), ) - target_message_id = list(message_ids.values())[0] if message_ids else None - if target_message_id: + # Every message_ids value must match the channel; first-only let a multimodel id slip past. + for target_message_id in (message_ids or {}).values(): + if not target_message_id: + continue target_message = await Messages.get_message_by_id(target_message_id) if target_message and target_message.channel_id != channel.id: raise HTTPException( diff --git a/backend/open_webui/socket/main.py b/backend/open_webui/socket/main.py index 2884847a0e..e048b00978 100644 --- a/backend/open_webui/socket/main.py +++ b/backend/open_webui/socket/main.py @@ -841,11 +841,15 @@ async def _make_channel_emitter(request_info): async def _emit_channel_update(content: str, done: bool = False): from open_webui.models.messages import MessageForm, Messages + # Fail closed: only update a message that actually belongs to this channel. + msg = await Messages.get_message_by_id(message_id) + if not msg or msg.channel_id != channel_id: + return + update_form = MessageForm(content=content) if done: # Merge done flag into existing meta (preserve model_id etc.) - msg = await Messages.get_message_by_id(message_id) - existing_meta = (msg.meta or {}) if msg else {} + existing_meta = msg.meta or {} update_form = MessageForm( content=content, meta={**existing_meta, 'done': True},