llm-security/tests/scanners/mcp-live-inspect-stdout-cap.test.mjs
Kjell Tore Guttormsen 207385fbbe fix(llm-security): scanner robustness — ReDoS, MCP-stdout DoS, redirect loop, atomic writes (#24,#53,#31,#25,#51)
#24 the HTML-obfuscation injection patterns had overlapping unbounded runs plus a required closing quote, so a non-closing input backtracked O(N^2) (~28.7s at the 512KB cap); quantifiers bounded, pathological input now 4ms. #53 mcp-live-inspect buffered MCP-server stdout via readline with no cap, so a hostile stdio server could exhaust memory / throw an uncaught RangeError; replaced with manual line buffering capped at 4MB that rejects pending RPCs and destroys stdout. #31 vsix-fetch's same-host redirect follower had no depth cap (loop hang); added depth>=5 cap mirroring the sibling fetcher.

#25/#51 mcp-description-cache and skill-registry wrote JSON via bare writeFileSync (non-atomic: concurrent load-modify-save loses updates, a torn read silently yields an empty registry); both now write a temp file then renameSync. Suite 1931/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01TcQyMTQfyrsAapaCMPxTtQ
2026-07-18 10:15:11 +02:00

36 lines
1.5 KiB
JavaScript

// mcp-live-inspect-stdout-cap.test.mjs — #53 (v7.8.3): a hostile MCP stdio
// server emitting a giant newline-less stdout line must not buffer unbounded
// memory or crash the inspector. The session must abort with a cap error.
// Zero external dependencies: node:test + node:assert only.
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { inspectServer } from '../../scanners/mcp-live-inspect.mjs';
describe('inspectServer — stdout line byte cap (v7.8.3 #53)', () => {
it('aborts on a giant newline-less stdout chunk instead of buffering unbounded', { timeout: 30_000 }, async () => {
// Child writes ~10MB with no newline, then stays alive so stdout does
// not close. Without a cap the inspector buffers everything and only
// fails via the generic RPC timeout.
const script = [
'const b = Buffer.alloc(65536, 120);',
'for (let i = 0; i < 160; i++) process.stdout.write(b);',
'setInterval(() => {}, 1000);',
].join(' ');
const descriptor = {
name: 'hostile-flood',
command: process.execPath,
args: ['-e', script],
env: {},
};
const result = await inspectServer(descriptor, 20_000);
assert.ok(result, 'expected a result object');
assert.ok(result.error, `expected an error result, got: ${JSON.stringify(result).slice(0, 200)}`);
assert.match(
result.error,
/stdout line exceeded/,
`expected stdout cap error (not a generic timeout), got: ${result.error}`,
);
});
});