@brutalist/mcp
Version:
Deploy Claude, Codex & Gemini CLI agents to demolish your work before users do. Real file analysis. Brutal honesty. Now with intelligent pagination.
287 lines โข 11.6 kB
JavaScript
import { Readable, Transform } from 'stream';
import { logger } from '../logger.js';
/**
* Utilities for fuzz testing streaming parsers with random chunking,
* corrupted data, and various edge cases
*/
export class StreamingFuzzHarness {
/**
* Split data into random-sized chunks
*/
randomChunker(data, minChunk = 1, maxChunk = 100) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
const chunks = [];
let offset = 0;
while (offset < buffer.length) {
const chunkSize = Math.floor(Math.random() * (maxChunk - minChunk + 1)) + minChunk;
const end = Math.min(offset + chunkSize, buffer.length);
chunks.push(buffer.slice(offset, end));
offset = end;
}
return chunks;
}
/**
* Create a readable stream that emits data in random chunks
*/
createRandomChunkStream(data, minChunk = 1, maxChunk = 100, delayMs = 0) {
const chunks = this.randomChunker(data, minChunk, maxChunk);
let index = 0;
return new Readable({
async read() {
if (index >= chunks.length) {
this.push(null); // End stream
return;
}
if (delayMs > 0) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
this.push(chunks[index++]);
}
});
}
/**
* Inject invalid UTF-8 sequences into a string
*/
corruptWithInvalidUtf8(data) {
const buffer = Buffer.from(data);
const corrupted = Buffer.allocUnsafe(buffer.length + 10);
buffer.copy(corrupted);
// Inject some invalid UTF-8 sequences
const invalidSequences = [
Buffer.from([0xFF, 0xFF]), // Invalid start bytes
Buffer.from([0xC0, 0x80]), // Overlong encoding
Buffer.from([0xED, 0xA0, 0x80]), // UTF-16 surrogate
Buffer.from([0xF4, 0x90, 0x80, 0x80]), // Code point > U+10FFFF
];
// Insert random invalid sequences
for (let i = 0; i < 3; i++) {
const pos = Math.floor(Math.random() * corrupted.length);
const invalidSeq = invalidSequences[Math.floor(Math.random() * invalidSequences.length)];
invalidSeq.copy(corrupted, pos);
}
return corrupted;
}
/**
* Truncate data at various boundaries to test partial parsing
*/
truncateAtBoundaries(data) {
const truncated = [];
// Truncate at different percentages
const percentages = [0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99];
for (const pct of percentages) {
const len = Math.floor(data.length * pct);
truncated.push(data.substring(0, len));
}
// Truncate in middle of likely JSON structures
const jsonBoundaries = ['{', '}', '[', ']', '"', ':', ','];
for (const boundary of jsonBoundaries) {
const index = data.indexOf(boundary);
if (index > 0 && index < data.length - 1) {
truncated.push(data.substring(0, index));
truncated.push(data.substring(0, index + 1));
}
}
return truncated;
}
/**
* Create a transform stream that randomly corrupts data
*/
createCorruptionStream(corruptionRate = 0.01) {
return new Transform({
transform(chunk, encoding, callback) {
const corrupted = Buffer.allocUnsafe(chunk.length);
for (let i = 0; i < chunk.length; i++) {
if (Math.random() < corruptionRate) {
// Corrupt this byte
corrupted[i] = Math.floor(Math.random() * 256);
}
else {
corrupted[i] = chunk[i];
}
}
callback(null, corrupted);
}
});
}
/**
* Create a transform stream that simulates slow delivery
*/
createThrottleStream(bytesPerSecond) {
let lastEmit = Date.now();
let bytesSent = 0;
return new Transform({
async transform(chunk, encoding, callback) {
const now = Date.now();
const elapsed = (now - lastEmit) / 1000;
const allowedBytes = Math.floor(elapsed * bytesPerSecond);
if (bytesSent >= allowedBytes) {
// Need to wait
const waitTime = ((bytesSent + chunk.length) / bytesPerSecond - elapsed) * 1000;
await new Promise(resolve => setTimeout(resolve, waitTime));
}
bytesSent += chunk.length;
callback(null, chunk);
}
});
}
/**
* Create a transform stream that simulates backpressure
*/
createBackpressureStream(bufferSize = 1024) {
let buffer = [];
let totalSize = 0;
let paused = false;
return new Transform({
transform(chunk, encoding, callback) {
buffer.push(chunk);
totalSize += chunk.length;
if (totalSize > bufferSize && !paused) {
paused = true;
logger.debug('StreamingFuzz: Simulating backpressure');
// Simulate processing delay
setTimeout(() => {
// Flush buffer
const combined = Buffer.concat(buffer);
buffer = [];
totalSize = 0;
paused = false;
callback(null, combined);
}, 100);
}
else if (!paused) {
callback(null, chunk);
}
}
});
}
/**
* Test a parser with various fuzzing strategies
*/
async fuzzTestParser(parser, validInput, options = {}) {
const results = { passed: 0, failed: 0, errors: [] };
// Test with valid input first
try {
parser(validInput);
results.passed++;
}
catch (error) {
results.failed++;
results.errors.push(new Error(`Failed on valid input: ${error.message}`));
}
// Test truncation
if (options.testTruncation) {
const truncated = this.truncateAtBoundaries(validInput);
for (const input of truncated) {
try {
parser(input);
// Parser should handle partial input gracefully
results.passed++;
}
catch (error) {
// Expected to fail on truncated input, but should not crash
if (error.message.includes('Unexpected end') ||
error.message.includes('Unexpected token') ||
error.message.includes('Unterminated')) {
results.passed++;
}
else {
results.failed++;
results.errors.push(new Error(`Unexpected error on truncated input: ${error.message}`));
}
}
}
}
// Test corruption
if (options.testCorruption) {
// Corrupt random characters
for (let i = 0; i < 10; i++) {
const corrupted = validInput.split('');
const pos = Math.floor(Math.random() * corrupted.length);
const charCode = Math.floor(Math.random() * 128);
corrupted[pos] = String.fromCharCode(charCode);
try {
parser(corrupted.join(''));
// Parser might succeed if corruption didn't affect structure
results.passed++;
}
catch (error) {
// Should handle corruption gracefully
if (!error.message.includes('Cannot read properties of undefined') &&
!error.message.includes('Maximum call stack')) {
results.passed++;
}
else {
results.failed++;
results.errors.push(new Error(`Parser crashed on corrupted input: ${error.message}`));
}
}
}
}
// Test invalid UTF-8
if (options.testInvalidUtf8) {
try {
const invalidUtf8 = this.corruptWithInvalidUtf8(validInput);
parser(invalidUtf8.toString('utf-8'));
results.passed++;
}
catch (error) {
// Should handle invalid UTF-8 gracefully
if (!error.message.includes('Cannot read properties of undefined')) {
results.passed++;
}
else {
results.failed++;
results.errors.push(new Error(`Parser crashed on invalid UTF-8: ${error.message}`));
}
}
}
return results;
}
/**
* Generate test cases for NDJSON streaming
*/
generateNdjsonTestCases() {
const cases = [];
// Valid NDJSON
cases.push('{"type":"message","content":"test"}\n{"type":"content_block_delta","delta":"more"}\n');
// Missing newlines
cases.push('{"type":"message","content":"test"}{"type":"content_block_delta","delta":"more"}');
// Extra newlines
cases.push('\n\n{"type":"message","content":"test"}\n\n\n{"type":"content_block_delta","delta":"more"}\n\n');
// Partial JSON at end
cases.push('{"type":"message","content":"test"}\n{"type":"content_block_delta"');
// Invalid JSON in middle
cases.push('{"type":"message","content":"test"}\n{invalid json}\n{"type":"content_block_delta","delta":"more"}\n');
// Unicode in content
cases.push('{"type":"message","content":"๐ ๆต่ฏ ใในใ"}\n{"type":"emoji","value":"๐"}\n');
// Very long lines
const longContent = 'x'.repeat(10000);
cases.push(`{"type":"message","content":"${longContent}"}\n`);
// Nested JSON structures
cases.push('{"type":"complex","data":{"nested":{"deep":{"value":123}}}}\n');
return cases;
}
/**
* Generate test cases for Codex JSON output
*/
generateCodexJsonTestCases() {
const cases = [];
// Valid Codex output
cases.push('[{"type":"thinking","content":"analyzing"},{"type":"agent_message","content":"result"}]');
// Only agent messages
cases.push('[{"type":"agent_message","content":"first"},{"type":"agent_message","content":"second"}]');
// Mixed with other types
cases.push('[{"type":"file_read","path":"/test"},{"type":"agent_message","content":"found"},{"type":"thinking","content":"done"}]');
// Empty array
cases.push('[]');
// Not an array
cases.push('{"type":"agent_message","content":"not in array"}');
// Malformed JSON
cases.push('[{"type":"agent_message","content":"unclosed"');
// Very large output
const largeContent = 'x'.repeat(100000);
cases.push(`[{"type":"agent_message","content":"${largeContent}"}]`);
return cases;
}
}
//# sourceMappingURL=streaming-fuzz.js.map