mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 03:14:00 +00:00
fix: stop reading MCP request bodies past the size limit
The HTTP handler read the whole body before checking its size, so a body without Content-Length could be buffered without bound. It now reads the stream and stops as soon as the body passes the limit.
This commit is contained in:
1 parent
6f56c84b0b
commit
1b3aa8de8b
2 files changed
+51
-3
No files matched your search
+31
-3
@@ -25,9 +25,8 @@ export function createMcpHttpHandler(server) {
|
|||||||
const declared = Number(request.headers.get('content-length'));
|
const declared = Number(request.headers.get('content-length'));
|
||||||
if (declared > MAX_BODY_BYTES)
|
if (declared > MAX_BODY_BYTES)
|
||||||
return json(413, { error: 'Request too large' });
|
return json(413, { error: 'Request too large' });
|
||||||
const text = await request.text();
|
const text = await readCapped(request, MAX_BODY_BYTES);
|
||||||
if (new TextEncoder().encode(text).length > MAX_BODY_BYTES)
|
if (text === null) return json(413, { error: 'Request too large' });
|
||||||
return json(413, { error: 'Request too large' });
|
|
||||||
let message;
|
let message;
|
||||||
try {
|
try {
|
||||||
message = JSON.parse(text);
|
message = JSON.parse(text);
|
||||||
@@ -39,6 +38,35 @@ export function createMcpHttpHandler(server) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a request body as text, stopping as soon as it passes `limit` bytes,
|
||||||
|
* so a body without a Content-Length cannot be buffered without bound.
|
||||||
|
* Returns null when the body is too large.
|
||||||
|
*/
|
||||||
|
async function readCapped(request, limit) {
|
||||||
|
if (!request.body) return '';
|
||||||
|
const reader = request.body.getReader();
|
||||||
|
const chunks = [];
|
||||||
|
let size = 0;
|
||||||
|
for (;;) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
size += value.byteLength;
|
||||||
|
if (size > limit) {
|
||||||
|
await reader.cancel().catch(() => {});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
chunks.push(value);
|
||||||
|
}
|
||||||
|
const bytes = new Uint8Array(size);
|
||||||
|
let offset = 0;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
bytes.set(chunk, offset);
|
||||||
|
offset += chunk.byteLength;
|
||||||
|
}
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
function json(status, body) {
|
function json(status, body) {
|
||||||
return new Response(JSON.stringify(body), {
|
return new Response(JSON.stringify(body), {
|
||||||
status,
|
status,
|
||||||
|
|||||||
@@ -176,6 +176,26 @@ test('the HTTP transport accepts one JSON message per POST', async () => {
|
|||||||
);
|
);
|
||||||
assert.equal((await post({}, { 'Content-Type': 'text/plain' })).status, 415);
|
assert.equal((await post({}, { 'Content-Type': 'text/plain' })).status, 415);
|
||||||
assert.equal((await post('x'.repeat(1024 * 1024 + 1))).status, 413);
|
assert.equal((await post('x'.repeat(1024 * 1024 + 1))).status, 413);
|
||||||
|
// A streamed body without Content-Length stops being read past the limit.
|
||||||
|
let produced = 0;
|
||||||
|
const chunk = new Uint8Array(64 * 1024).fill(32);
|
||||||
|
const body = new ReadableStream({
|
||||||
|
pull(controller) {
|
||||||
|
if (produced >= 8 * 1024 * 1024) return controller.close();
|
||||||
|
produced += chunk.byteLength;
|
||||||
|
controller.enqueue(chunk);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const streamed = await handle(
|
||||||
|
new Request('http://localhost/mcp', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body,
|
||||||
|
duplex: 'half',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
assert.equal(streamed.status, 413);
|
||||||
|
assert.ok(produced <= 1024 * 1024 + 4 * chunk.byteLength, `read ${produced}`);
|
||||||
const get = await handle(new Request('http://localhost/mcp'));
|
const get = await handle(new Request('http://localhost/mcp'));
|
||||||
assert.equal(get.status, 405);
|
assert.equal(get.status, 405);
|
||||||
assert.equal(get.headers.get('allow'), 'POST');
|
assert.equal(get.headers.get('allow'), 'POST');
|
||||||
|
|||||||
Reference in new issue
Block a user