From 33e4e0dcc43afcca80f9c635d762cdc76c768ba9 Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 1 Jun 2026 22:37:32 +0200 Subject: [PATCH] fix: gate chat_completion channel: branch on channel access + message scoping (#24725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: gate chat_completion channel: branch on channel access + message scoping When chat_id starts with 'channel:' the chat-completion handler skips the chat ownership / storage block below it. Nothing replaced that gate. The downstream channel emitter in socket/main.py:_make_channel_ emitter writes to Messages.update_message_by_id using a caller-supplied message_id pulled from form_data['id'], with no membership check, no write-access check, and no validation that the message_id belongs to the channel. Net effect: any authenticated user could submit chat_id='channel:' + id='' and overwrite that message with attacker-controlled LLM output. Cross- channel writes worked too — private channels, DMs, channels the caller has no access to. Original author attribution stayed intact on the overwritten row. Add the missing checks at the channel: branch: 1. Channel must exist (404 otherwise). 2. Non-admin caller must have write access to the channel — membership for group/dm channels, AccessGrants permission='write' for others. 3. The message_id (if supplied) must belong to the same channel — a caller with write access to channel A cannot use this path to overwrite a message in channel B. Behaviour change is limited to callers who were exploiting the gap: legitimate flows that supply a message_id under their own channel membership continue to work unchanged. Co-authored-by: sfwani * chore: trim verbose comment on channel: branch gate --------- Co-authored-by: sfwani --- backend/open_webui/main.py | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 12541ff66e..7611ca454b 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -470,8 +470,11 @@ from open_webui.env import ( WEBUI_SESSION_COOKIE_SECURE, ) from open_webui.internal.db import ScopedSession, engine, get_async_session +from open_webui.models.access_grants import AccessGrants +from open_webui.models.channels import Channels from open_webui.models.chats import ChatForm, Chats from open_webui.models.functions import Functions +from open_webui.models.messages import Messages from open_webui.models.models import Models from open_webui.models.users import UserModel, Users from open_webui.routers import ( @@ -1802,6 +1805,44 @@ async def chat_completion( if metadata.get('chat_id') and user: chat_id = metadata['chat_id'] + + # Gate channel: branch — caller needs write access on the channel + # and the supplied message_id must belong to that channel. + if chat_id.startswith('channel:'): + channel_id = chat_id.removeprefix('channel:') + channel = await Channels.get_channel_by_id(channel_id) + if not channel: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=ERROR_MESSAGES.NOT_FOUND, + ) + if user.role != 'admin': + if channel.type in ['group', 'dm']: + if not await Channels.is_user_channel_member(channel.id, user.id): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.DEFAULT(), + ) + else: + if not await AccessGrants.has_access( + user_id=user.id, + resource_type='channel', + resource_id=channel.id, + permission='write', + ): + raise HTTPException( + 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: + target_message = await Messages.get_message_by_id(target_message_id) + if target_message and target_message.channel_id != channel.id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail=ERROR_MESSAGES.DEFAULT(), + ) + if not chat_id.startswith('local:') and not chat_id.startswith( 'channel:' ): # temporary/channel chats are not stored