From f5d7b3f6183a35f963a5f4116ddae73fe10cb374 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Wed, 28 Jan 2026 01:42:57 -0600 Subject: [PATCH] fix(backend): use single UUID for storage and database file records Previously, write_file() generated a UUID for storage paths and let Prisma auto-generate a separate UUID for the database record. This caused download URLs to return 404 because the storage layer extracted the wrong ID. Now the same UUID is used for both, fixing the download URL issue. Also consolidates MAX_FILE_SIZE_BYTES into Config.max_file_size_mb setting for consistent configuration across file.py, workspace.py, and workspace_tools.py. Co-Authored-By: Claude Opus 4.5 --- .../features/chat/tools/workspace_tools.py | 8 +++++--- .../backend/backend/data/workspace.py | 3 +++ autogpt_platform/backend/backend/util/file.py | 19 ++++++++++--------- .../backend/backend/util/settings.py | 7 +++++++ .../backend/backend/util/workspace.py | 11 ++++++----- 5 files changed, 31 insertions(+), 17 deletions(-) diff --git a/autogpt_platform/backend/backend/api/features/chat/tools/workspace_tools.py b/autogpt_platform/backend/backend/api/features/chat/tools/workspace_tools.py index d9d12042d5..a435ff04a9 100644 --- a/autogpt_platform/backend/backend/api/features/chat/tools/workspace_tools.py +++ b/autogpt_platform/backend/backend/api/features/chat/tools/workspace_tools.py @@ -10,7 +10,8 @@ from pydantic import BaseModel from backend.api.features.chat.model import ChatSession from backend.data.workspace import get_or_create_workspace from backend.util.virus_scanner import scan_content_safe -from backend.util.workspace import MAX_FILE_SIZE_BYTES, WorkspaceManager +from backend.util.settings import Config +from backend.util.workspace import WorkspaceManager from .base import BaseTool from .models import ErrorResponse, ResponseType, ToolResponseBase @@ -468,9 +469,10 @@ class WriteWorkspaceFileTool(BaseTool): ) # Check size - if len(content) > MAX_FILE_SIZE_BYTES: + max_file_size = Config().max_file_size_mb * 1024 * 1024 + if len(content) > max_file_size: return ErrorResponse( - message=f"File too large. Maximum size is {MAX_FILE_SIZE_BYTES // (1024*1024)}MB", + message=f"File too large. Maximum size is {Config().max_file_size_mb}MB", session_id=session_id, ) diff --git a/autogpt_platform/backend/backend/data/workspace.py b/autogpt_platform/backend/backend/data/workspace.py index a354d3a3c3..1c439e2786 100644 --- a/autogpt_platform/backend/backend/data/workspace.py +++ b/autogpt_platform/backend/backend/data/workspace.py @@ -55,6 +55,7 @@ async def get_workspace(user_id: str) -> Optional[UserWorkspace]: async def create_workspace_file( workspace_id: str, + file_id: str, name: str, path: str, storage_path: str, @@ -71,6 +72,7 @@ async def create_workspace_file( Args: workspace_id: The workspace ID + file_id: The file ID (same as used in storage path for consistency) name: User-visible filename path: Virtual path (e.g., "/documents/report.pdf") storage_path: Actual storage path (GCS or local) @@ -91,6 +93,7 @@ async def create_workspace_file( file = await UserWorkspaceFile.prisma().create( data={ + "id": file_id, "workspaceId": workspace_id, "name": name, "path": path, diff --git a/autogpt_platform/backend/backend/util/file.py b/autogpt_platform/backend/backend/util/file.py index 538fe59df6..42de0fa6c2 100644 --- a/autogpt_platform/backend/backend/util/file.py +++ b/autogpt_platform/backend/backend/util/file.py @@ -12,6 +12,7 @@ from prisma.enums import WorkspaceFileSource from backend.util.cloud_storage import get_cloud_storage_handler from backend.util.request import Requests +from backend.util.settings import Config from backend.util.type import MediaFileType from backend.util.virus_scanner import scan_content_safe @@ -130,7 +131,7 @@ async def store_media_file( base_path.mkdir(parents=True, exist_ok=True) # Security fix: Add disk space limits to prevent DoS - MAX_FILE_SIZE = 100 * 1024 * 1024 # 100MB per file + MAX_FILE_SIZE_BYTES = Config().max_file_size_mb * 1024 * 1024 MAX_TOTAL_DISK_USAGE = 1024 * 1024 * 1024 # 1GB total per execution directory # Check total disk usage in base_path @@ -210,9 +211,9 @@ async def store_media_file( raise ValueError(f"Invalid file path '{filename}': {e}") from e # Check file size limit - if len(workspace_content) > MAX_FILE_SIZE: + if len(workspace_content) > MAX_FILE_SIZE_BYTES: raise ValueError( - f"File too large: {len(workspace_content)} bytes > {MAX_FILE_SIZE} bytes" + f"File too large: {len(workspace_content)} bytes > {MAX_FILE_SIZE_BYTES} bytes" ) # Virus scan the workspace content before writing locally @@ -235,9 +236,9 @@ async def store_media_file( raise ValueError(f"Invalid file path '{filename}': {e}") from e # Check file size limit - if len(cloud_content) > MAX_FILE_SIZE: + if len(cloud_content) > MAX_FILE_SIZE_BYTES: raise ValueError( - f"File too large: {len(cloud_content)} bytes > {MAX_FILE_SIZE} bytes" + f"File too large: {len(cloud_content)} bytes > {MAX_FILE_SIZE_BYTES} bytes" ) # Virus scan the cloud content before writing locally @@ -265,9 +266,9 @@ async def store_media_file( content = base64.b64decode(b64_content) # Check file size limit - if len(content) > MAX_FILE_SIZE: + if len(content) > MAX_FILE_SIZE_BYTES: raise ValueError( - f"File too large: {len(content)} bytes > {MAX_FILE_SIZE} bytes" + f"File too large: {len(content)} bytes > {MAX_FILE_SIZE_BYTES} bytes" ) # Virus scan the base64 content before writing @@ -287,9 +288,9 @@ async def store_media_file( resp = await Requests().get(file) # Check file size limit - if len(resp.content) > MAX_FILE_SIZE: + if len(resp.content) > MAX_FILE_SIZE_BYTES: raise ValueError( - f"File too large: {len(resp.content)} bytes > {MAX_FILE_SIZE} bytes" + f"File too large: {len(resp.content)} bytes > {MAX_FILE_SIZE_BYTES} bytes" ) # Virus scan the downloaded content before writing diff --git a/autogpt_platform/backend/backend/util/settings.py b/autogpt_platform/backend/backend/util/settings.py index d7c15a16d8..aa28a4c9ac 100644 --- a/autogpt_platform/backend/backend/util/settings.py +++ b/autogpt_platform/backend/backend/util/settings.py @@ -395,6 +395,13 @@ class Config(UpdateTrackingModel["Config"], BaseSettings): description="Maximum file size in MB for file uploads (1-1024 MB)", ) + max_file_size_mb: int = Field( + default=100, + ge=1, + le=1024, + description="Maximum file size in MB for workspace files (1-1024 MB)", + ) + # AutoMod configuration automod_enabled: bool = Field( default=False, diff --git a/autogpt_platform/backend/backend/util/workspace.py b/autogpt_platform/backend/backend/util/workspace.py index ce46a1e646..4cf81da779 100644 --- a/autogpt_platform/backend/backend/util/workspace.py +++ b/autogpt_platform/backend/backend/util/workspace.py @@ -22,13 +22,11 @@ from backend.data.workspace import ( list_workspace_files, soft_delete_workspace_file, ) +from backend.util.settings import Config from backend.util.workspace_storage import compute_file_checksum, get_workspace_storage logger = logging.getLogger(__name__) -# Maximum file size: 100MB per file -MAX_FILE_SIZE_BYTES = 100 * 1024 * 1024 - class WorkspaceManager: """ @@ -160,10 +158,11 @@ class WorkspaceManager: ValueError: If file exceeds size limit or path already exists """ # Enforce file size limit - if len(content) > MAX_FILE_SIZE_BYTES: + max_file_size = Config().max_file_size_mb * 1024 * 1024 + if len(content) > max_file_size: raise ValueError( f"File too large: {len(content)} bytes exceeds " - f"{MAX_FILE_SIZE_BYTES // (1024*1024)}MB limit" + f"{Config().max_file_size_mb}MB limit" ) # Determine path with session scoping @@ -209,6 +208,7 @@ class WorkspaceManager: try: file = await create_workspace_file( workspace_id=self.workspace_id, + file_id=file_id, name=filename, path=path, storage_path=storage_path, @@ -229,6 +229,7 @@ class WorkspaceManager: # Retry the create file = await create_workspace_file( workspace_id=self.workspace_id, + file_id=file_id, name=filename, path=path, storage_path=storage_path,