From 5ef02cdeccbb4d8519a4e7fedbdf758350b13163 Mon Sep 17 00:00:00 2001 From: Benebo7 Date: Thu, 30 Jul 2026 00:05:07 -0300 Subject: [PATCH] feat: add max file size env providing customizable limitation --- docker/.env.example | 4 ++++ docker/docker-compose.dev.yml | 2 ++ docker/docker-compose.yml | 2 ++ surfsense_backend/app/routes/documents_routes.py | 5 ++++- .../components/providers/runtime-config.server.tsx | 1 + .../components/providers/runtime-config.tsx | 1 + .../components/sources/DocumentUploadTab.tsx | 12 ++++++------ 7 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 625cb48d5..b9d544f97 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -93,6 +93,10 @@ CERT_EMAIL= # If a CDN/load balancer sits in front of Caddy, narrow this to that proxy's CIDRs. # TRUSTED_PROXIES=0.0.0.0/0 # SURFSENSE_MAX_BODY_SIZE=5GB +# Per-file cap for authenticated document uploads (MB). Keep it at or below +# SURFSENSE_MAX_BODY_SIZE above, which limits the whole request at the proxy. +# Passed to both the backend (enforcement) and the frontend (pre-upload check). +# MAX_FILE_SIZE_MB=500 # # Browser API and Zero URLs are same-origin relative behind bundled Caddy. # Next.js server-side calls use Docker DNS through SURFSENSE_BACKEND_INTERNAL_URL diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 03c46f6d2..2995a4ab8 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -127,6 +127,7 @@ services: - NEXT_FRONTEND_URL=${NEXT_FRONTEND_URL:-http://localhost:3000} - WHATSAPP_BRIDGE_URL=${WHATSAPP_BRIDGE_URL:-http://whatsapp-bridge:9929} - SEARXNG_URL=${SEARXNG_URL:-http://searxng:8080} + - MAX_FILE_SIZE_MB=${MAX_FILE_SIZE_MB:-500} # Daytona Sandbox – uncomment and set credentials to enable cloud code execution # - DAYTONA_SANDBOX_ENABLED=TRUE # - DAYTONA_API_KEY=${DAYTONA_API_KEY:-} @@ -268,6 +269,7 @@ services: ETL_SERVICE: ${ETL_SERVICE:-DOCLING} DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-self-hosted} SURFSENSE_BACKEND_INTERNAL_URL: http://backend:8000 + MAX_FILE_SIZE_MB: ${MAX_FILE_SIZE_MB:-500} depends_on: backend: condition: service_healthy diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2b3f8ece2..71a000a6a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -148,6 +148,7 @@ services: BACKEND_URL: ${BACKEND_URL:-${SURFSENSE_PUBLIC_URL:-http://localhost:${LISTEN_HTTP_PORT:-3929}}} WHATSAPP_BRIDGE_URL: ${WHATSAPP_BRIDGE_URL:-http://whatsapp-bridge:9929} SEARXNG_URL: ${SEARXNG_URL:-http://searxng:8080} + MAX_FILE_SIZE_MB: ${MAX_FILE_SIZE_MB:-500} # Daytona Sandbox – uncomment and set credentials to enable cloud code execution # DAYTONA_SANDBOX_ENABLED: "TRUE" # DAYTONA_API_KEY: ${DAYTONA_API_KEY:-} @@ -295,6 +296,7 @@ services: ETL_SERVICE: ${ETL_SERVICE:-DOCLING} DEPLOYMENT_MODE: ${DEPLOYMENT_MODE:-self-hosted} SURFSENSE_BACKEND_INTERNAL_URL: http://backend:8000 + MAX_FILE_SIZE_MB: ${MAX_FILE_SIZE_MB:-500} labels: - "com.centurylinklabs.watchtower.enable=true" depends_on: diff --git a/surfsense_backend/app/routes/documents_routes.py b/surfsense_backend/app/routes/documents_routes.py index 574bf78b5..fdd8df7a2 100644 --- a/surfsense_backend/app/routes/documents_routes.py +++ b/surfsense_backend/app/routes/documents_routes.py @@ -56,7 +56,10 @@ logger = logging.getLogger(__name__) router = APIRouter() -MAX_FILE_SIZE_BYTES = 500 * 1024 * 1024 # 500 MB per file +# Per-file upload cap. Operators raise MAX_FILE_SIZE_MB when self-hosting on +# hardware that can take it; the frontend reads the same value for its +# pre-upload check. +MAX_FILE_SIZE_BYTES = int(os.getenv("MAX_FILE_SIZE_MB", "500")) * 1024 * 1024 @router.post("/documents") diff --git a/surfsense_web/components/providers/runtime-config.server.tsx b/surfsense_web/components/providers/runtime-config.server.tsx index c515820c2..3ad9bfb6f 100644 --- a/surfsense_web/components/providers/runtime-config.server.tsx +++ b/surfsense_web/components/providers/runtime-config.server.tsx @@ -13,6 +13,7 @@ export async function RuntimeConfig({ children }: { children: React.ReactNode }) authType: process.env.AUTH_TYPE ?? BUILD_TIME_AUTH_TYPE, etlService: process.env.ETL_SERVICE ?? BUILD_TIME_ETL_SERVICE, deploymentMode: process.env.DEPLOYMENT_MODE ?? BUILD_TIME_DEPLOYMENT_MODE, + maxFileSizeMB: Number(process.env.MAX_FILE_SIZE_MB ?? "500") }; return {children}; diff --git a/surfsense_web/components/providers/runtime-config.tsx b/surfsense_web/components/providers/runtime-config.tsx index 574573f6f..e64eda298 100644 --- a/surfsense_web/components/providers/runtime-config.tsx +++ b/surfsense_web/components/providers/runtime-config.tsx @@ -9,6 +9,7 @@ export interface RuntimeConfigValue { authType: AuthType; etlService: string; deploymentMode: DeploymentMode; + maxFileSizeMB: number; } const RuntimeConfigContext = createContext(null); diff --git a/surfsense_web/components/sources/DocumentUploadTab.tsx b/surfsense_web/components/sources/DocumentUploadTab.tsx index 9ce27ff11..dced0e78d 100644 --- a/surfsense_web/components/sources/DocumentUploadTab.tsx +++ b/surfsense_web/components/sources/DocumentUploadTab.tsx @@ -121,8 +121,7 @@ function flattenTree( const FOLDER_BATCH_SIZE_BYTES = 20 * 1024 * 1024; const FOLDER_BATCH_MAX_FILES = 10; -const MAX_FILE_SIZE_MB = 500; -const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024; + const toggleRowClass = "flex items-center justify-between rounded-lg bg-slate-400/5 dark:bg-white/5 p-3"; @@ -133,7 +132,7 @@ export function DocumentUploadTab({ onAccordionStateChange, }: DocumentUploadTabProps) { const t = useTranslations("upload_documents"); - const { etlService } = useRuntimeConfig(); + const { etlService, maxFileSizeMB } = useRuntimeConfig(); const [files, setFiles] = useState([]); const [uploadProgress, setUploadProgress] = useState(0); const [accordionValue, setAccordionValue] = useState(""); @@ -146,7 +145,8 @@ export function DocumentUploadTab({ const progressIntervalRef = useRef | null>(null); const [folderUpload, setFolderUpload] = useState(null); const [isFolderUploading, setIsFolderUploading] = useState(false); - + const MAX_FILE_SIZE_BYTES = maxFileSizeMB * 1024 * 1024; + useEffect(() => { return () => { if (progressIntervalRef.current) { @@ -175,7 +175,7 @@ export function DocumentUploadTab({ toast.error(t("file_too_large"), { description: t("file_too_large_desc", { name: oversized[0].name, - maxMB: MAX_FILE_SIZE_MB, + maxMB: maxFileSizeMB, }), }); } @@ -191,7 +191,7 @@ export function DocumentUploadTab({ return [...prev, ...newEntries]; }); }, - [t] + [t, MAX_FILE_SIZE_BYTES, maxFileSizeMB] ); const onDrop = useCallback(