From c75fe8e74b72617c51282cc3ea0a2e8d9cdd9140 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Tue, 19 May 2026 20:33:46 +0400 Subject: [PATCH] fix: get_image_base64_from_file_id --- backend/open_webui/routers/channels.py | 2 +- backend/open_webui/utils/files.py | 41 +++++++++++++------------- backend/open_webui/utils/middleware.py | 6 ++-- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/backend/open_webui/routers/channels.py b/backend/open_webui/routers/channels.py index 68fc71fe5a..c987b8a5db 100644 --- a/backend/open_webui/routers/channels.py +++ b/backend/open_webui/routers/channels.py @@ -934,7 +934,7 @@ async def model_response_handler(request, channel, message, user, db=None): if file.get('type', '') == 'image': images.append(file.get('url', '')) elif file.get('content_type', '').startswith('image/'): - image = await get_image_base64_from_file_id(file.get('id', '')) + image = await get_image_base64_from_file_id(file.get('id', ''), user=user) if image: images.append(image) diff --git a/backend/open_webui/utils/files.py b/backend/open_webui/utils/files.py index 76492eeae1..b267593980 100644 --- a/backend/open_webui/utils/files.py +++ b/backend/open_webui/utils/files.py @@ -22,6 +22,7 @@ from open_webui.models.chats import Chats from open_webui.models.files import Files from open_webui.retrieval.web.utils import validate_url from open_webui.routers.files import upload_file_handler +from open_webui.utils.access_control.files import has_access_to_file from open_webui.routers.images import ( get_image_data, upload_image, @@ -50,7 +51,7 @@ _IMAGE_MIME_FALLBACK = { } -async def get_image_base64_from_url(url: str) -> Optional[str]: +async def get_image_base64_from_url(url: str, user=None) -> Optional[str]: try: if url.startswith('http'): # Validate URL to prevent SSRF attacks against local/private networks. @@ -70,25 +71,9 @@ async def get_image_base64_from_url(url: str) -> Optional[str]: content_type = response.headers.get('Content-Type', 'image/png') return f'data:{content_type};base64,{encoded_string}' else: - file = await Files.get_file_by_id(url) - - if not file: - return None - - file_path = await asyncio.to_thread(Storage.get_file, file.path) - file_path = Path(file_path) - - if file_path.is_file(): - with open(file_path, 'rb') as image_file: - encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - content_type = mimetypes.guess_type(file_path.name)[0] or (file.meta or {}).get('content_type') - if not content_type and ENABLE_IMAGE_CONTENT_TYPE_EXTENSION_FALLBACK: - content_type = _IMAGE_MIME_FALLBACK.get(file_path.suffix.lower()) - if not content_type: - return None - return f'data:{content_type};base64,{encoded_string}' - else: - return None + # Non-URL string — treat as file_id. Delegate to the canonical + # file-ID resolver which enforces ownership/access checks. + return await get_image_base64_from_file_id(url, user=user) except Exception as e: return None @@ -194,11 +179,25 @@ async def get_file_url_from_base64(request, base64_file_string, metadata, user): return None -async def get_image_base64_from_file_id(id: str) -> Optional[str]: +async def get_image_base64_from_file_id(id: str, user=None) -> Optional[str]: file = await Files.get_file_by_id(id) if not file: return None + # Gate file-by-id resolution by ownership to prevent exfiltration. + # A caller could place another user's file_id in an image_url field; + # without this check the server reads the file from disk, inlines it + # base64 into the LLM request, and the content leaks via OCR/describe. + # Owner, admin, and explicit read-grant holders are allowed. + if user is None: + return None + if ( + file.user_id != user.id + and user.role != 'admin' + and not await has_access_to_file(file.id, 'read', user) + ): + return None + try: file_path = await asyncio.to_thread(Storage.get_file, file.path) file_path = Path(file_path) diff --git a/backend/open_webui/utils/middleware.py b/backend/open_webui/utils/middleware.py index 1921bab1bd..a6a9697c18 100644 --- a/backend/open_webui/utils/middleware.py +++ b/backend/open_webui/utils/middleware.py @@ -2096,7 +2096,7 @@ def apply_params_to_form_data(form_data, model): return form_data -async def convert_url_images_to_base64(form_data): +async def convert_url_images_to_base64(form_data, user=None): messages = form_data.get('messages', []) for message in messages: @@ -2117,7 +2117,7 @@ async def convert_url_images_to_base64(form_data): continue try: - base64_data = await get_image_base64_from_url(image_url) + base64_data = await get_image_base64_from_url(image_url, user=user) if base64_data: new_content.append( { @@ -2340,7 +2340,7 @@ async def process_chat_payload(request, form_data, user, metadata, model): except Exception: pass - form_data = await convert_url_images_to_base64(form_data) + form_data = await convert_url_images_to_base64(form_data, user=user) event_emitter = await get_event_emitter(metadata) event_caller = await get_event_call(metadata)