From 8b904959b74bddbb4a771e42a45d0065c50dcdcb Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Fri, 1 Aug 2025 17:36:01 +0200 Subject: [PATCH 1/2] Validate 'next-action' header format in server action requests --- packages/gitbook/src/middleware.ts | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 5af3b631d1..669625a09f 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -64,21 +64,35 @@ export async function middleware(request: NextRequest) { } async function validateServerActionRequest(request: NextRequest) { - // We need to reject incorrect server actions requests - // We do not do it in cloudflare workers as there is a bug that prevents us from reading the request body. - if (request.headers.has('next-action') && process.env.GITBOOK_RUNTIME !== 'cloudflare') { - // We just test that the json body is parseable - try { - const clonedRequest = request.clone(); - await clonedRequest.json(); - } catch (e) { - console.warn('Invalid server action request', e); - // If the body is not parseable, we reject the request + // First thing we need to do is validate that the header is in a correct format. + if (request.headers.has('next-action')) { + // A server action id is a 1byte hex string followed by a 20 bytes SHA1 hash + // For ref https://github.com/vercel/next.js/blob/db561cb924cbea0f3384e89f251fc443a8aec1ae/crates/next-custom-transforms/src/transforms/server_actions.rs#L266-L268 + const regex = /^[a-f0-9]{42}$/; + const match = request.headers.get('next-action')?.match(regex); + if (!match) { return new Response('Invalid request', { status: 400, headers: { 'content-type': 'text/plain' }, }); } + + // We need to reject incorrect server actions requests + // We do not do it in cloudflare workers as there is a bug that prevents us from reading the request body. + if (process.env.GITBOOK_RUNTIME !== 'cloudflare') { + // We just test that the json body is parseable + try { + const clonedRequest = request.clone(); + await clonedRequest.json(); + } catch (e) { + console.warn('Invalid server action request', e); + // If the body is not parseable, we reject the request + return new Response('Invalid request', { + status: 400, + headers: { 'content-type': 'text/plain' }, + }); + } + } } } From 30fc171bd1cd31bdb2d7b6ae04ddf7f6848abee0 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 5 Aug 2025 10:11:43 +0200 Subject: [PATCH 2/2] review --- packages/gitbook/src/middleware.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gitbook/src/middleware.ts b/packages/gitbook/src/middleware.ts index 669625a09f..6237903a57 100644 --- a/packages/gitbook/src/middleware.ts +++ b/packages/gitbook/src/middleware.ts @@ -66,9 +66,9 @@ export async function middleware(request: NextRequest) { async function validateServerActionRequest(request: NextRequest) { // First thing we need to do is validate that the header is in a correct format. if (request.headers.has('next-action')) { - // A server action id is a 1byte hex string followed by a 20 bytes SHA1 hash + // A server action id is a 1-byte hex string (2 chars) followed by a 20-byte SHA1 hash (40 chars) = 42 total characters. // For ref https://github.com/vercel/next.js/blob/db561cb924cbea0f3384e89f251fc443a8aec1ae/crates/next-custom-transforms/src/transforms/server_actions.rs#L266-L268 - const regex = /^[a-f0-9]{42}$/; + const regex = /^[a-fA-F0-9]{42}$/; const match = request.headers.get('next-action')?.match(regex); if (!match) { return new Response('Invalid request', {