Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions packages/gitbook-v2/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(normalized.toString());
}

// Reject malicious requests
const rejectResponse = await validateServerActionRequest(request);
if (rejectResponse) {
return rejectResponse;
}

for (const handler of [serveSiteRoutes, serveSpacePDFRoutes]) {
const result = await handler(requestURL, request);
if (result) {
Expand All @@ -56,6 +62,25 @@ 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
return new Response('Invalid request', {
status: 400,
headers: { 'content-type': 'text/plain' },
});
}
}
}

/**
* Handle request that are targetting the site routes group.
*/
Expand All @@ -82,6 +107,14 @@ async function serveSiteRoutes(requestURL: URL, request: NextRequest) {
});
}

// We want to filter hostnames that contains a port here as this is likely a malicious request.
if (siteRequestURL.host.includes(':')) {
return new Response('Invalid request', {
status: 400,
headers: { 'content-type': 'text/plain' },
});
}

//
// Detect and extract the visitor authentication token from the request
//
Expand Down