From b70438a2cfe0e454ea31984b6f344e2ae7f05571 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 17 May 2026 11:15:31 +0000 Subject: [PATCH] fix: guard chat completion error writes Co-authored-by: mkatwi --- backend/open_webui/main.py | 34 +++++++++++++- .../test/apps/webui/routers/test_chats.py | 46 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/main.py b/backend/open_webui/main.py index 544756a6e8f4..9663ad6e852e 100644 --- a/backend/open_webui/main.py +++ b/backend/open_webui/main.py @@ -457,6 +457,16 @@ from open_webui.utils.redis import get_sentinels_from_env +def can_update_chat(chat_id: Optional[str], user: UserModel) -> bool: + if not chat_id: + return False + + if user.role == "admin" and ENABLE_ADMIN_CHAT_ACCESS: + return Chats.get_chat_by_id(chat_id) is not None + + return Chats.get_chat_by_id_and_user_id(chat_id, user.id) is not None + + if SAFE_MODE: print("SAFE MODE ENABLED") Functions.deactivate_all_functions() @@ -1351,6 +1361,12 @@ async def chat_completion( ), } + if metadata.get("chat_id") and not can_update_chat(metadata["chat_id"], user): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Chat not found", + ) + request.state.metadata = metadata form_data["metadata"] = metadata @@ -1360,7 +1376,11 @@ async def chat_completion( except Exception as e: log.debug(f"Error processing chat payload: {e}") - if metadata.get("chat_id") and metadata.get("message_id"): + if ( + metadata.get("chat_id") + and metadata.get("message_id") + and can_update_chat(metadata["chat_id"], user) + ): # Update the chat message with the error Chats.upsert_message_to_chat_by_id_and_message_id( metadata["chat_id"], @@ -1370,6 +1390,9 @@ async def chat_completion( }, ) + if isinstance(e, HTTPException): + raise e + raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), @@ -1383,7 +1406,11 @@ async def chat_completion( ) except Exception as e: log.debug(f"Error in chat completion: {e}") - if metadata.get("chat_id") and metadata.get("message_id"): + if ( + metadata.get("chat_id") + and metadata.get("message_id") + and can_update_chat(metadata["chat_id"], user) + ): # Update the chat message with the error Chats.upsert_message_to_chat_by_id_and_message_id( metadata["chat_id"], @@ -1393,6 +1420,9 @@ async def chat_completion( }, ) + if isinstance(e, HTTPException): + raise e + raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), diff --git a/backend/open_webui/test/apps/webui/routers/test_chats.py b/backend/open_webui/test/apps/webui/routers/test_chats.py index a36a01fb1497..1c666aa2ae4a 100644 --- a/backend/open_webui/test/apps/webui/routers/test_chats.py +++ b/backend/open_webui/test/apps/webui/routers/test_chats.py @@ -234,3 +234,49 @@ def test_delete_shared_chat_by_id(self): chat = self.chats.get_chat_by_id(chat_id) assert chat.share_id is None + + def test_chat_completion_error_does_not_update_other_users_chat(self): + from open_webui.models.chats import ChatForm + + victim_message_id = "victim-message" + victim_chat = self.chats.insert_new_chat( + "2", + ChatForm( + chat={ + "title": "Victim chat", + "history": { + "currentId": victim_message_id, + "messages": { + victim_message_id: { + "id": victim_message_id, + "role": "assistant", + "content": "do not overwrite", + } + }, + }, + } + ), + ) + + with mock_webui_user(id="3", role="user"): + response = self.fast_api_client.post( + "/api/chat/completions", + json={ + "model": "attacker-direct", + "model_item": { + "id": "attacker-direct", + "direct": True, + "owned_by": "openai", + "info": {"meta": {}}, + }, + "chat_id": victim_chat.id, + "id": victim_message_id, + }, + ) + + assert response.status_code == 403 + message = self.chats.get_message_by_id_and_message_id( + victim_chat.id, victim_message_id + ) + assert message["content"] == "do not overwrite" + assert "error" not in message