asa / scripts /lib /chat /safe_chatbot.py
defenestrate's picture
ASA Space: squashed history (instructions and settings live in the private pack)
526950e
Raw History Blame Contribute Delete
2.17 kB
"""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)