fix(proxy): enforce body size limit on actual byteLength, not just Content-Length header
Chunked requests omit Content-Length entirely. The previous guard only checked the header and was bypassed. Now the body is buffered first and its byteLength is checked, catching both cases. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -35,11 +35,21 @@ async function proxy(event: Parameters<RequestHandler>[0]): Promise<Response> {
|
||||
|
||||
const hasBody = !NO_BODY_METHODS.has(event.request.method);
|
||||
|
||||
// Early exit: reject well-behaved clients that advertise an oversized body
|
||||
// before we buffer anything.
|
||||
const contentLength = event.request.headers.get('Content-Length');
|
||||
if (contentLength && parseInt(contentLength, 10) > 1_048_576) {
|
||||
return new Response('Payload Too Large', { status: 413 });
|
||||
}
|
||||
|
||||
// Buffer the body so we can check its actual size. This also catches chunked
|
||||
// requests that omit Content-Length entirely (parseInt(null) → NaN → passes
|
||||
// the header check above).
|
||||
const bodyBuffer = hasBody ? await event.request.arrayBuffer() : undefined;
|
||||
if (bodyBuffer && bodyBuffer.byteLength > 1_048_576) {
|
||||
return new Response('Payload Too Large', { status: 413 });
|
||||
}
|
||||
|
||||
// Only forward Content-Type from the browser request — other headers (Cookie,
|
||||
// Authorization, Accept-Encoding, etc.) must not be forwarded directly.
|
||||
// Authentication is injected by handleFetch in hooks.server.ts from the
|
||||
@@ -51,7 +61,7 @@ async function proxy(event: Parameters<RequestHandler>[0]): Promise<Response> {
|
||||
const response = await event.fetch(backendUrl, {
|
||||
method: event.request.method,
|
||||
headers: requestHeaders,
|
||||
body: hasBody ? await event.request.arrayBuffer() : undefined
|
||||
body: bodyBuffer
|
||||
});
|
||||
|
||||
// Forward all response headers except hop-by-hop so that Content-Disposition,
|
||||
|
||||
@@ -69,6 +69,21 @@ describe('catch-all API proxy — body size limit', () => {
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns 413 when actual body exceeds 1 MB even if Content-Length header is absent or lying', async () => {
|
||||
const mockFetch = vi.fn();
|
||||
const event = makeEvent('documents', 'POST', mockFetch);
|
||||
(event.request as Request) = new Request('http://localhost/api/documents', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/octet-stream', 'Content-Length': '0' },
|
||||
body: new Uint8Array(1_048_577) // 1 MB + 1 byte, but Content-Length says 0
|
||||
});
|
||||
|
||||
const response = await POST(event as never);
|
||||
|
||||
expect(response.status).toBe(413);
|
||||
expect(mockFetch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('forwards request when Content-Length header is absent', async () => {
|
||||
const mockFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }));
|
||||
const event = makeEvent('documents', 'POST', mockFetch);
|
||||
|
||||
Reference in New Issue
Block a user