diff --git a/src/shared/middleware/bodySizeGuard.ts b/src/shared/middleware/bodySizeGuard.ts index af1167458b..fcf2afdb16 100644 --- a/src/shared/middleware/bodySizeGuard.ts +++ b/src/shared/middleware/bodySizeGuard.ts @@ -28,6 +28,9 @@ export const MAX_BODY_BYTES_AUDIO = 100 * 1024 * 1024; /** Larger limit for file uploads: 500 MB */ export const MAX_BODY_BYTES_FILE = 500 * 1024 * 1024; +/** Larger limit for LLM request payloads: 50 MB */ +export const MAX_BODY_BYTES_LLM_API = 50 * 1024 * 1024; + /** Configured limit — reads from env or falls back to 10 MB */ export const MAX_BODY_BYTES = parseRequestBodyLimitBytes(process.env.MAX_BODY_SIZE_BYTES); @@ -35,6 +38,8 @@ type BodySizeRule = { prefix: string; limit: number }; const ROUTE_LIMITS: BodySizeRule[] = [ { prefix: "/api/db-backups/import", limit: MAX_BODY_BYTES_IMPORT }, + { prefix: "/api/v1/chat/completions", limit: MAX_BODY_BYTES_LLM_API }, + { prefix: "/api/v1/responses", limit: MAX_BODY_BYTES_LLM_API }, { prefix: "/api/v1/audio/transcriptions", limit: MAX_BODY_BYTES_AUDIO }, { prefix: "/api/v1/files", limit: MAX_BODY_BYTES_FILE }, ]; diff --git a/src/shared/utils/inputSanitizer.ts b/src/shared/utils/inputSanitizer.ts index fdfe8af507..bdc927f5d8 100644 --- a/src/shared/utils/inputSanitizer.ts +++ b/src/shared/utils/inputSanitizer.ts @@ -62,8 +62,8 @@ const INJECTION_PATTERNS = [ * latency/GC source. Injection directives sit near the top of a prompt, so * scanning hundreds of KB of pasted code / RAG context buys only CPU. We bound * the scan to the first 16 KB (generous: real directives are far shorter) before - * the regex loop. The 10 MB body-size cap that protects ingestion lives - * elsewhere; this constant only bounds the regex scan. Refs #3932 / #4041. + * the regex loop. The body-size caps that protect ingestion live elsewhere; + * this constant only bounds the regex scan. Refs #3932 / #4041. */ export const MAX_INJECTION_SCAN_BYTES = 16 * 1024; diff --git a/tests/unit/body-size-guard.test.ts b/tests/unit/body-size-guard.test.ts index bcb90126ab..1f4d8ae03b 100644 --- a/tests/unit/body-size-guard.test.ts +++ b/tests/unit/body-size-guard.test.ts @@ -4,6 +4,7 @@ import * as bodySizeGuard from "../../src/shared/middleware/bodySizeGuard.ts"; import { MAX_BODY_BYTES_AUDIO, MAX_BODY_BYTES_FILE, + MAX_BODY_BYTES_LLM_API, getBodySizeLimit, checkBodySize, } from "../../src/shared/middleware/bodySizeGuard.ts"; @@ -23,6 +24,14 @@ test("body size guard uses maxBodySizeMb from settings for regular API routes", }); test("body size guard keeps dedicated upload limits as lower bounds", () => { + assert.equal( + getBodySizeLimit("/api/v1/responses", { maxBodySizeMb: 10 }), + MAX_BODY_BYTES_LLM_API + ); + assert.equal( + getBodySizeLimit("/api/v1/chat/completions", { maxBodySizeMb: 10 }), + MAX_BODY_BYTES_LLM_API + ); assert.equal( getBodySizeLimit("/api/v1/audio/transcriptions", { maxBodySizeMb: 1 }), MAX_BODY_BYTES_AUDIO @@ -49,6 +58,32 @@ test("checkBodySize reports the configured request limit in 413 responses", asyn assert.match(body.error.message, /100 MB/); }); +test("/api/v1/responses route guard allows 15 MB agent payloads by default", () => { + const fifteenMb = 15 * 1024 * 1024; + const request = new Request("http://localhost/api/v1/responses", { + method: "POST", + headers: { "content-length": String(fifteenMb) }, + }); + + assert.equal(checkBodySize(request, getBodySizeLimit("/api/v1/responses")), null); +}); + +test("/api/v1/responses route guard rejects payloads above the LLM API floor", async () => { + const tooBig = MAX_BODY_BYTES_LLM_API + 1; + const request = new Request("http://localhost/api/v1/responses", { + method: "POST", + headers: { "content-length": String(tooBig) }, + }); + + const response = checkBodySize(request, getBodySizeLimit("/api/v1/responses")); + + assert.ok(response); + assert.equal(response.status, 413); + const body = await response.json(); + assert.equal(body.error.code, "PAYLOAD_TOO_LARGE"); + assert.match(body.error.message, /50 MB/); +}); + test("/api/v1/files route has 512 MB dedicated limit floor", () => { const limit = getBodySizeLimit("/api/v1/files", { maxBodySizeMb: 1 }); assert.equal(limit, MAX_BODY_BYTES_FILE);