import test from 'node:test'; import assert from 'node:assert/strict'; import { generateMutationSuite, auditApiFuzzResilience, BOUNDARY_INTEGER_PAYLOADS, PROTOTYPE_POLLUTION_PAYLOADS } from '../dist/fuzz/api_fuzz.js'; import { synthesizeAndInjectPatch, synthesizeAndVerifyPatch } from '../dist/patch/patch_engine.js'; import { runInCanarySandbox } from '../dist/sandbox/canary_sandbox.js'; import { executeCanaryVerification } from '../dist/sandbox/canary_runner.js'; import { createVulnForgeServer } from '../dist/mcp/server.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; test('Fuzz Generator: generateMutationSuite generates all strategy test cases', () => { const fields = { id: 'number', name: 'string', metadata: 'object' }; const mutations = generateMutationSuite(fields, 'ALL', 5); assert.ok(mutations.length > 10, 'Should generate multiple mutation cases'); assert.ok( mutations.some((m) => m.mutationType === 'BOUNDARY_INT'), 'Should include boundary integer mutations' ); assert.ok( mutations.some((m) => m.mutationType === 'TYPE_CONFUSION'), 'Should include type confusion mutations' ); assert.ok( mutations.some((m) => m.mutationType === 'PROTOTYPE_POLLUTION'), 'Should include prototype pollution mutations' ); assert.ok( mutations.some((m) => m.mutationType === 'OVERSIZED_PAYLOAD' || m.mutationType === 'MALFORMED_ENCODING'), 'Should include encoding / oversized payload mutations' ); }); test('API Fuzz Auditor: Detects missing schema validation on request body (CWE-20)', () => { const code = ` app.post('/api/checkout', (req, res) => { const { itemId, quantity, userToken } = req.body; res.json({ status: 'ok', itemId, quantity }); }); `; const result = auditApiFuzzResilience({ routeCode: code }); assert.equal(result.complianceStatus, 'FAIL'); assert.ok( result.findings.some((f) => f.id === 'API-FUZZ-MISSING-SCHEMA-VALIDATION'), 'Should report missing schema validation finding' ); assert.equal(result.summary.missingSchemaValidationCount, 1); }); test('API Fuzz Auditor: Detects prototype pollution in unsafe object merge (CWE-1321)', () => { const code = ` app.post('/api/settings', (req, res) => { const userSettings = {}; Object.assign(userSettings, req.body); res.json({ updated: true }); }); `; const result = auditApiFuzzResilience({ routeCode: code }); assert.equal(result.complianceStatus, 'FAIL'); assert.ok( result.findings.some((f) => f.id === 'API-FUZZ-PROTOTYPE-POLLUTION'), 'Should detect prototype pollution finding' ); assert.equal(result.summary.prototypePollutionRisks, 1); }); test('API Fuzz Auditor: Detects unhandled async exception in route handler (CWE-754)', () => { const code = ` app.post('/api/transfer', async (req, res) => { const result = await bankService.sendMoney(req.body); res.json(result); }); `; const result = auditApiFuzzResilience({ routeCode: code }); assert.ok( result.findings.some((f) => f.id === 'API-FUZZ-UNHANDLED-ASYNC-EXCEPTION'), 'Should detect unhandled async exception risk' ); assert.equal(result.summary.unhandledExceptionRisks, 1); }); test('API Fuzz Auditor: Detects unrestricted JSON body parser size (CWE-400)', () => { const code = ` const app = express(); app.use(express.json()); `; const result = auditApiFuzzResilience({ routeCode: code }); assert.ok( result.findings.some((f) => f.id === 'API-FUZZ-BODY-PARSER-UNRESTRICTED-SIZE'), 'Should detect unrestricted body parser size' ); assert.equal(result.summary.oversizedPayloadDosRisks, 1); }); test('API Fuzz Auditor: Detects unbounded integer parsing (CWE-190)', () => { const code = ` app.get('/api/items', (req, res) => { const offset = parseInt(req.query.offset); res.json({ offset }); }); `; const result = auditApiFuzzResilience({ routeCode: code }); assert.ok( result.findings.some((f) => f.id === 'API-FUZZ-UNBOUNDED-INTEGER-PARSE'), 'Should detect unbounded integer parse' ); assert.equal(result.summary.typeConfusionRisks, 1); }); test('API Fuzz Auditor: Fully hardened and validated API endpoint passes cleanly', () => { const hardenedCode = ` import { z } from 'zod'; const UserSchema = z.object({ id: z.number().int().min(1).max(10000), username: z.string().min(3).max(50) }).strict(); app.post('/api/user', async (req, res, next) => { try { const validated = UserSchema.parse(req.body); const safeTarget = JSON.parse(JSON.stringify(validated, (k, v) => (k === '__proto__' ? undefined : v))); res.json({ user: safeTarget }); } catch (err) { next(err); } }); `; const result = auditApiFuzzResilience({ routeCode: hardenedCode }); assert.equal(result.complianceStatus, 'PASS'); assert.equal(result.totalFindings, 0); }); test('Patch Engine: Injects Zod schema validation guard into vulnerable handler', () => { const vulnerable = ` const { username, email } = req.body; `; const patched = synthesizeAndInjectPatch({ sourceCode: vulnerable, filename: 'routes/user.ts' }); assert.equal(patched.applied, true); assert.ok(patched.patchedCode.includes("import { z } from 'zod';")); assert.ok(patched.patchedCode.includes('BodySchema = z.object')); assert.ok(patched.findingsResolved.includes('API-FUZZ-MISSING-SCHEMA-VALIDATION')); }); test('Patch Engine: Injects safe prototype pollution guard before Object.assign', () => { const vulnerable = ` const target = {}; Object.assign(target, req.body); `; const patched = synthesizeAndInjectPatch({ sourceCode: vulnerable, filename: 'routes/proto.ts' }); assert.equal(patched.applied, true); assert.ok(patched.patchedCode.includes('const safeBody = JSON.parse')); assert.ok(patched.patchedCode.includes('__proto__')); assert.ok(patched.findingsResolved.includes('API-FUZZ-PROTOTYPE-POLLUTION')); }); test('Patch Engine: Restricts body parser size limit to 100kb', () => { const vulnerable = ` app.use(express.json()); `; const patched = synthesizeAndInjectPatch({ sourceCode: vulnerable, filename: 'server.ts' }); assert.equal(patched.applied, true); assert.ok(patched.patchedCode.includes("express.json({ limit: '100kb' })")); assert.ok(patched.findingsResolved.includes('API-FUZZ-BODY-PARSER-UNRESTRICTED-SIZE')); }); test('Canary Sandbox: MockFuzzHarness records route invocations and validates prototype security', async () => { const code = ` fuzzHarness.testRoute(() => {}, { body: { title: 'safe_post', __proto__: { polluted: false } } }); `; const result = await runInCanarySandbox(code); assert.equal(result.success, true); assert.equal(result.fuzzHarness.executedRoutes.length, 1); assert.equal(result.fuzzHarness.executedRoutes[0].body.title, 'safe_post'); }); test('Canary Verification: Dual canary verifies API fuzzing patch mitigates vulnerability and preserves benign requests', async () => { const original = ` const target = {}; Object.assign(target, req.body); `; const patched = ` const target = {}; const safeBody = JSON.parse(JSON.stringify(req.body, (k, v) => (k === '__proto__' ? undefined : v))); Object.assign(target, safeBody); `; const canaryReport = await executeCanaryVerification({ originalCode: original, patchedCode: patched, vulnType: 'API_FUZZ_RESILIENCE', attackPayload: '{"__proto__":{"polluted":true}}', benignPayload: '{"name":"valid_user"}' }); assert.equal(canaryReport.canaryStatus, 'VERIFIED_SECURE'); assert.equal(canaryReport.attackBlocked, true); assert.equal(canaryReport.benignPreserved, true); }); test('MCP Server: Registers vulnforge_fuzz_api tool with comprehensive schema', async () => { const server = createVulnForgeServer(); const listHandler = server._requestHandlers.get('tools/list'); assert.ok(listHandler, 'ListTools handler must exist'); const result = await listHandler({ method: 'tools/list', params: {} }); const fuzzTool = result.tools.find((t) => t.name === 'vulnforge_fuzz_api'); assert.ok(fuzzTool, 'vulnforge_fuzz_api tool must be registered'); assert.ok(fuzzTool.inputSchema.properties.routeCode, 'routeCode property must be defined'); assert.ok(fuzzTool.inputSchema.properties.fuzzStrategy, 'fuzzStrategy property must be defined'); }); test('MCP Server: vulnforge_fuzz_api executes and returns valid JSON security report', async () => { const server = createVulnForgeServer(); const callHandler = server._requestHandlers.get('tools/call'); assert.ok(callHandler, 'CallTool handler must exist'); const sampleRoute = `app.post('/api/item', (req, res) => { const { name } = req.body; res.json({ name }); });`; const response = await callHandler( { method: 'tools/call', params: { name: 'vulnforge_fuzz_api', arguments: { routeCode: sampleRoute } } }, {} ); assert.ok(response.content && response.content[0].text); const parsed = JSON.parse(response.content[0].text); assert.equal(parsed.complianceStatus, 'FAIL'); assert.ok(parsed.totalFindings > 0); assert.ok(parsed.summary.endpointsAudited >= 1); }); test('MCP Server: vulnforge_full_scan processes fuzzRouteCode and attaches findings', async () => { const server = createVulnForgeServer(); const callHandler = server._requestHandlers.get('tools/call'); assert.ok(callHandler, 'CallTool handler must exist'); const sampleRoute = `app.post('/api/order', (req, res) => { const { amount } = req.body; res.json({ amount }); });`; const response = await callHandler( { method: 'tools/call', params: { name: 'vulnforge_full_scan', arguments: { fuzzRouteCode: sampleRoute } } }, {} ); assert.ok(response.content && response.content[0].text); const summary = JSON.parse(response.content[0].text); assert.ok(summary.totalFindings > 0); assert.ok(summary.findings.some((f) => f.category === 'api_fuzzing')); });