From d11735d4cd4030c3134b17696a2e05c96e334804 Mon Sep 17 00:00:00 2001 From: Narciso Date: Tue, 11 Aug 2026 14:24:35 -0400 Subject: [PATCH 1/3] fix(api): configurable body size limits for OpenAPI catch-all route --- apps/dokploy/pages/api/[...trpc].ts | 28 +++++++++++++++++++++++++- packages/server/src/constants/index.ts | 12 +++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/pages/api/[...trpc].ts b/apps/dokploy/pages/api/[...trpc].ts index 83ff9b050..b7140e96f 100644 --- a/apps/dokploy/pages/api/[...trpc].ts +++ b/apps/dokploy/pages/api/[...trpc].ts @@ -1,4 +1,8 @@ -import { validateRequest } from "@dokploy/server"; +import { + OPENAPI_MAX_JSON_BODY_SIZE, + OPENAPI_MAX_UPLOAD_SIZE, + validateRequest, +} from "@dokploy/server"; import { createOpenApiNextHandler } from "@dokploy/trpc-openapi"; import type { NextApiRequest, NextApiResponse } from "next"; import { appRouter } from "@/server/api/root"; @@ -12,10 +16,26 @@ 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); + const isMultipart = req.headers["content-type"]?.startsWith( + "multipart/form-data", + ); + const limit = isMultipart + ? OPENAPI_MAX_UPLOAD_SIZE + : OPENAPI_MAX_JSON_BODY_SIZE; + + if (contentLength > limit) { + res.status(413).json({ message: "Payload too large" }); + return; + } + // @ts-ignore return createOpenApiNextHandler({ router: appRouter, createContext: createTRPCContext, + maxBodySize: OPENAPI_MAX_JSON_BODY_SIZE, onError: process.env.NODE_ENV === "development" ? ({ path, error }: { path: string | undefined; error: Error }) => { @@ -28,3 +48,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => { }; export default handler; + +export const config = { + api: { + bodyParser: false, + }, +}; diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index 51ffeb8c4..4f56173cf 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -13,6 +13,18 @@ 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 + +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 + type DockerSocketCandidate = { label: string; path: string; From 8144e1a2fe563ec502d134f736a3f7f539d05311 Mon Sep 17 00:00:00 2001 From: Narciso Date: Tue, 11 Aug 2026 14:57:42 -0400 Subject: [PATCH 2/3] 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; From 529f54e2a52738c23e2c6266eb983b31d32091e5 Mon Sep 17 00:00:00 2001 From: Narciso Date: Tue, 11 Aug 2026 15:06:02 -0400 Subject: [PATCH 3/3] fix(api): reject non-integer body size env vars --- packages/server/src/constants/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/constants/index.ts b/packages/server/src/constants/index.ts index 506fdf1c2..c303041f3 100644 --- a/packages/server/src/constants/index.ts +++ b/packages/server/src/constants/index.ts @@ -17,8 +17,8 @@ export const CLEANUP_CRON_JOB = "50 23 * * *"; 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) { + const parsed = Number(raw); + if (!Number.isInteger(parsed) || parsed <= 0) { console.warn(`Invalid ${envVar}="${raw}", using default ${fallback}`); return fallback; }