"""A Chatbot that never turns message text into file downloads. Gradio 4.44.1's ``Chatbot(type="messages")`` checks every space-separated word of every message with ``Path(word).is_file()`` and, on a match, copies that file into its cache and sends it to the browser as a download. Since the user's own message is echoed into the chatbot, any logged-in user could type a server path (``/proc/self/environ``, a knowledge-pack file) and download it, secrets included. Our chats only ever show text, so this subclass keeps the message and drops the file extraction. It also refuses to move any file or URL into its cache. A crafted request can put a file entry in the chat history it sends; for a URL, Gradio downloads it on the server and hands back the result (a server-side request forgery). Refusing the move stops both before anything is read or fetched. """ from __future__ import annotations import gradio as gr from gradio.components.chatbot import ChatMessage, Message class TextOnlyChatbot(gr.Chatbot): # The browser picks a component's code by this name. A subclass would # report "textonlychatbot", which the page then tries to load as a # custom component that doesn't exist, and it spins forever after login. # It is the stock Chatbot on the page; only the server side differs. def get_block_name(self) -> str: return "chatbot" def move_resource_to_block_cache(self, url_or_file_path): # type: ignore[override] raise gr.Error("This chat accepts text only.") async def async_move_resource_to_block_cache(self, url_or_file_path): # type: ignore[override] raise gr.Error("This chat accepts text only.") def _postprocess_message_messages(self, message): # type: ignore[override] if isinstance(message, dict): message["content"] = self._postprocess_content(message["content"]) return [Message(**message)] if isinstance(message, ChatMessage): content = self._postprocess_content(message.content) return [Message(role=message.role, content=content, metadata=message.metadata)] return super()._postprocess_message_messages(message)