| 66 | } |
| 67 | |
| 68 | async function validateServerActionRequest(request: NextRequest) { |
| 69 | // First thing we need to do is validate that the header is in a correct format. |
| 70 | if (request.headers.has('next-action')) { |
| 71 | // A server action id is a 1-byte hex string (2 chars) followed by a 20-byte SHA1 hash (40 chars) = 42 total characters. |
| 72 | // For ref https://github.com/vercel/next.js/blob/db561cb924cbea0f3384e89f251fc443a8aec1ae/crates/next-custom-transforms/src/transforms/server_actions.rs#L266-L268 |
| 73 | const regex = /^[a-fA-F0-9]{42}$/; |
| 74 | const match = request.headers.get('next-action')?.match(regex); |
| 75 | if (!match) { |
| 76 | return new Response('Invalid request', { |
| 77 | status: 400, |
| 78 | headers: { 'content-type': 'text/plain' }, |
| 79 | }); |
| 80 | } |
| 81 | |
| 82 | // We need to reject incorrect server actions requests |
| 83 | // We do not do it in cloudflare workers as there is a bug that prevents us from reading the request body. |
| 84 | if (process.env.GITBOOK_RUNTIME !== 'cloudflare') { |
| 85 | // We just test that the json body is parseable |
| 86 | try { |
| 87 | const clonedRequest = request.clone(); |
| 88 | await clonedRequest.json(); |
| 89 | } catch (e) { |
| 90 | console.warn('Invalid server action request', e); |
| 91 | // If the body is not parseable, we reject the request |
| 92 | return new Response('Invalid request', { |
| 93 | status: 400, |
| 94 | headers: { 'content-type': 'text/plain' }, |
| 95 | }); |
| 96 | } |
| 97 | } |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | /** |
| 102 | * Filter malicious requests. |