reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
141
reverse-skill-repo/burp-mcp-full/test/mcp-bridge.test.js
Normal file
141
reverse-skill-repo/burp-mcp-full/test/mcp-bridge.test.js
Normal file
@@ -0,0 +1,141 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { once } = require('node:events');
|
||||
const http = require('node:http');
|
||||
const net = require('node:net');
|
||||
const { spawn } = require('node:child_process');
|
||||
const { test } = require('node:test');
|
||||
const path = require('node:path');
|
||||
|
||||
class LineReader {
|
||||
constructor(stream) {
|
||||
this.lines = [];
|
||||
this.waiters = [];
|
||||
this.buffer = '';
|
||||
stream.setEncoding('utf8');
|
||||
stream.on('data', chunk => {
|
||||
this.buffer += chunk;
|
||||
const parts = this.buffer.split('\n');
|
||||
this.buffer = parts.pop();
|
||||
for (const line of parts) {
|
||||
this.push(line.replace(/\r$/, ''));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
push(line) {
|
||||
const waiter = this.waiters.shift();
|
||||
if (waiter) {
|
||||
waiter.resolve(line);
|
||||
} else {
|
||||
this.lines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
next(timeoutMs = 5000) {
|
||||
if (this.lines.length > 0) return Promise.resolve(this.lines.shift());
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
const index = this.waiters.indexOf(waiter);
|
||||
if (index >= 0) this.waiters.splice(index, 1);
|
||||
reject(new Error('Timed out waiting for a line'));
|
||||
}, timeoutMs);
|
||||
const waiter = {
|
||||
resolve: line => {
|
||||
clearTimeout(timeout);
|
||||
resolve(line);
|
||||
}
|
||||
};
|
||||
this.waiters.push(waiter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getFreePort() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
server.once('error', reject);
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
const { port } = server.address();
|
||||
server.close(() => resolve(port));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function close(server) {
|
||||
if (!server) return Promise.resolve();
|
||||
return new Promise(resolve => server.close(() => resolve()));
|
||||
}
|
||||
|
||||
test('reconnects after Burp starts and handles a later tool call', async () => {
|
||||
const port = await getFreePort();
|
||||
const bridgePath = path.join(__dirname, '..', 'mcp-bridge.js');
|
||||
const bridge = spawn(process.execPath, [bridgePath], {
|
||||
env: {
|
||||
...process.env,
|
||||
BURP_MCP_HOST: '127.0.0.1',
|
||||
BURP_MCP_PORT: String(port),
|
||||
BURP_MCP_TOKEN: 'test-token'
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
const stdout = new LineReader(bridge.stdout);
|
||||
const stderr = new LineReader(bridge.stderr);
|
||||
let server;
|
||||
const requests = [];
|
||||
|
||||
try {
|
||||
const warning = await stderr.next();
|
||||
assert.match(warning, /Cannot connect to Burp/);
|
||||
|
||||
server = http.createServer((request, response) => {
|
||||
if (request.method === 'GET' && request.url === '/tools') {
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify(['proxy_history']));
|
||||
return;
|
||||
}
|
||||
|
||||
let body = '';
|
||||
request.on('data', chunk => { body += chunk; });
|
||||
request.on('end', () => {
|
||||
requests.push(JSON.parse(body));
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify({ ok: true }));
|
||||
});
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(port, '127.0.0.1', resolve);
|
||||
});
|
||||
|
||||
bridge.stdin.write(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method: 'tools/list',
|
||||
params: {}
|
||||
}) + '\n');
|
||||
const listResponse = JSON.parse(await stdout.next());
|
||||
assert.equal(listResponse.id, 1);
|
||||
assert.equal(listResponse.result.tools[0].name, 'burp_proxy_history');
|
||||
|
||||
bridge.stdin.write(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 2,
|
||||
method: 'tools/call',
|
||||
params: { name: 'burp_proxy_history', arguments: { limit: 1 } }
|
||||
}) + '\n');
|
||||
const callResponse = JSON.parse(await stdout.next());
|
||||
assert.equal(callResponse.id, 2);
|
||||
assert.deepEqual(requests[0], { tool: 'proxy_history', params: { limit: 1 } });
|
||||
assert.match(callResponse.result.content[0].text, /"ok": true/);
|
||||
} finally {
|
||||
const childClosed = bridge.exitCode === null && bridge.signalCode === null
|
||||
? once(bridge, 'close')
|
||||
: Promise.resolve();
|
||||
bridge.stdin.end();
|
||||
bridge.kill();
|
||||
await close(server);
|
||||
await childClosed.catch(() => {});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user