From 8144e1a2fe563ec502d134f736a3f7f539d05311 Mon Sep 17 00:00:00 2001 From: Narciso Date: Tue, 11 Aug 2026 14:57:42 -0400 Subject: [PATCH] fix(api): validate OpenAPI body size limits against Greptile findings --- apps/dokploy/pages/api/[...trpc].ts | 13 ++++++++---- packages/server/src/constants/index.ts | 28 +++++++++++++++++--------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/dokploy/pages/api/[...trpc].ts b/apps/dokploy/pages/api/[...trpc].ts index b7140e96f..dbad15922 100644 --- a/apps/dokploy/pages/api/[...trpc].ts +++ b/apps/dokploy/pages/api/[...trpc].ts @@ -16,17 +16,22 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { return; } - // getMultipartBody (trpc-openapi) doesn't accept maxBodySize, so multipart - // uploads have no cap unless enforced here before the handler reads the stream. - const contentLength = Number(req.headers["content-length"] ?? 0); + // getMultipartBody doesn't accept maxBodySize, so we cap it here instead. + const contentLength = Number(req.headers["content-length"]); const isMultipart = req.headers["content-type"]?.startsWith( "multipart/form-data", ); + + if (isMultipart && !Number.isFinite(contentLength)) { + res.status(411).json({ message: "Content-Length required" }); + return; + } + const limit = isMultipart ? OPENAPI_MAX_UPLOAD_SIZE : OPENAPI_MAX_JSON_BODY_SIZE; - if (contentLength > limit) { + if (Number.isFinite(contentLength) && contentLength > limit) { res.status(413).json({ message: "Payload too large" }); return; } diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index 4f56173cf..506fdf1c2 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -14,16 +14,26 @@ export const DOKPLOY_DOCKER_PORT = process.env.DOKPLOY_DOCKER_PORT export const CLEANUP_CRON_JOB = "50 23 * * *"; // Body size limits for the OpenAPI catch-all route (pages/api/[...trpc].ts). -// JSON/urlencoded bodies are capped by trpc-openapi's own default (100kb) unless -// we pass an explicit limit; multipart uploads (e.g. drop-deployment zips) have -// no built-in cap at all, so we enforce one manually via content-length. -export const OPENAPI_MAX_JSON_BODY_SIZE = process.env.OPENAPI_MAX_JSON_BODY_SIZE - ? Number.parseInt(process.env.OPENAPI_MAX_JSON_BODY_SIZE, 10) - : 10 * 1024 * 1024; // 10mb +const parseByteSize = (envVar: string, fallback: number): number => { + const raw = process.env[envVar]; + if (!raw) return fallback; + const parsed = Number.parseInt(raw, 10); + if (!Number.isFinite(parsed) || parsed <= 0) { + console.warn(`Invalid ${envVar}="${raw}", using default ${fallback}`); + return fallback; + } + return parsed; +}; -export const OPENAPI_MAX_UPLOAD_SIZE = process.env.OPENAPI_MAX_UPLOAD_SIZE - ? Number.parseInt(process.env.OPENAPI_MAX_UPLOAD_SIZE, 10) - : 1024 * 1024 * 1024; // 1gb +export const OPENAPI_MAX_JSON_BODY_SIZE = parseByteSize( + "OPENAPI_MAX_JSON_BODY_SIZE", + 10 * 1024 * 1024, // 10mb +); + +export const OPENAPI_MAX_UPLOAD_SIZE = parseByteSize( + "OPENAPI_MAX_UPLOAD_SIZE", + 1024 * 1024 * 1024, // 1gb +); type DockerSocketCandidate = { label: string;