mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +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'));
|
||||
if (declared > MAX_BODY_BYTES)
|
||||
return json(413, { error: 'Request too large' });
|
||||
const text = await request.text();
|
||||
if (new TextEncoder().encode(text).length > MAX_BODY_BYTES)
|
||||
return json(413, { error: 'Request too large' });
|
||||
const text = await readCapped(request, MAX_BODY_BYTES);
|
||||
if (text === null) return json(413, { error: 'Request too large' });
|
||||
let message;
|
||||
try {
|
||||
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) {
|
||||
return new Response(JSON.stringify(body), {
|
||||
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('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'));
|
||||
assert.equal(get.status, 405);
|
||||
assert.equal(get.headers.get('allow'), 'POST');
|
||||
|
||||
Reference in new issue
Block a user