diff --git a/gateway/src/http/routes/pairing-proxy.ts b/gateway/src/http/routes/pairing-proxy.ts index f5a0fe8c96d..6863dcc81b0 100644 --- a/gateway/src/http/routes/pairing-proxy.ts +++ b/gateway/src/http/routes/pairing-proxy.ts @@ -69,16 +69,25 @@ export function createPairingProxyHandler(config: GatewayConfig) { const hasBody = req.method !== "GET" && req.method !== "HEAD"; - // Payload size guard — reject oversized requests before buffering. + // Payload size guard — primary defense: reject via Content-Length before + // reading the body into memory. This is the main protection against + // oversized requests because Bun's Request.arrayBuffer() buffers the + // entire body with no streaming-limit API, so once we call it the + // memory is already allocated. if (hasBody) { const contentLength = req.headers.get("content-length"); - if (contentLength && Number(contentLength) > MAX_PAIRING_PAYLOAD_BYTES) { - log.warn({ contentLength }, "Pairing proxy payload too large (content-length)"); - return Response.json({ error: "Payload too large" }, { status: 413 }); + if (contentLength) { + const declared = Number(contentLength); + if (declared > MAX_PAIRING_PAYLOAD_BYTES || Number.isNaN(declared)) { + log.warn({ contentLength }, "Pairing proxy payload too large (content-length)"); + return Response.json({ error: "Payload too large" }, { status: 413 }); + } } } const bodyBuffer = hasBody ? await req.arrayBuffer() : null; + // Belt-and-suspenders: verify actual size after read in case + // Content-Length was absent (chunked) or spoofed. if (bodyBuffer !== null) { if (bodyBuffer.byteLength > MAX_PAIRING_PAYLOAD_BYTES) { log.warn({ bodyLength: bodyBuffer.byteLength }, "Pairing proxy payload too large");