// network.test.mjs — Integration tests for the network-mapper // Tests against the evil-project-health fixture which contains URLs to: // - ngrok-free.app (SUSPICIOUS_DOMAINS — HIGH) // - webhook.site (SUSPICIOUS_DOMAINS — HIGH) // - requestbin.com (SUSPICIOUS_DOMAINS — HIGH) // - pipedream.net (SUSPICIOUS_DOMAINS — HIGH) // - pastebin.com (SUSPICIOUS_DOMAINS — HIGH) // - bit.ly (SUSPICIOUS_DOMAINS — HIGH, URL shortener) // - 192.168.x.x (private IP — MEDIUM) // - 45.33.32.156 (public IP — HIGH, bypasses DNS) // // We do NOT assert on DNS resolution — it is network-dependent. // Only URL pattern detection (Phase 1–2 of the scanner) is tested. import { describe, it, beforeEach } from 'node:test'; import assert from 'node:assert/strict'; import { resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { resetCounter } from '../../scanners/lib/output.mjs'; import { discoverFiles } from '../../scanners/lib/file-discovery.mjs'; import { scan } from '../../scanners/network-mapper.mjs'; const __dirname = fileURLToPath(new URL('.', import.meta.url)); const FIXTURE = resolve(__dirname, '../../examples/malicious-skill-demo/evil-project-health'); describe('network-mapper integration', () => { let discovery; beforeEach(async () => { resetCounter(); discovery = await discoverFiles(FIXTURE); }); it('returns status ok', async () => { const result = await scan(FIXTURE, discovery); assert.equal(result.status, 'ok', `Expected status 'ok', got '${result.status}'`); }); it('scans at least one file', async () => { const result = await scan(FIXTURE, discovery); assert.ok(result.files_scanned >= 1, `Expected files_scanned >= 1, got ${result.files_scanned}`); }); it('detects at least 2 suspicious domain findings', async () => { const result = await scan(FIXTURE, discovery); const suspiciousFindings = result.findings.filter(f => f.severity === 'high'); assert.ok( suspiciousFindings.length >= 2, `Expected >= 2 HIGH severity network findings, got ${suspiciousFindings.length}. ` + `All findings: ${result.findings.map(f => `${f.severity}: ${f.title}`).join('; ')}` ); }); it('reports total findings >= 2', async () => { const result = await scan(FIXTURE, discovery); assert.ok( result.findings.length >= 2, `Expected >= 2 network findings, got ${result.findings.length}` ); }); it('detects ngrok-free.app as suspicious endpoint', async () => { const result = await scan(FIXTURE, discovery); const ngrokFinding = result.findings.find( f => f.title.toLowerCase().includes('ngrok') || (f.evidence && f.evidence.toLowerCase().includes('ngrok')) ); assert.ok( ngrokFinding, `Should detect ngrok-free.app. All titles: ${result.findings.map(f => f.title).join('; ')}` ); }); it('detects webhook.site as suspicious endpoint', async () => { const result = await scan(FIXTURE, discovery); const webhookFinding = result.findings.find( f => f.title.toLowerCase().includes('webhook.site') || (f.evidence && f.evidence.toLowerCase().includes('webhook.site')) ); assert.ok( webhookFinding, `Should detect webhook.site. All titles: ${result.findings.map(f => f.title).join('; ')}` ); }); it('suspicious domain findings reference owasp LLM02', async () => { const result = await scan(FIXTURE, discovery); const suspiciousFindings = result.findings.filter( f => f.severity === 'high' && f.title.toLowerCase().includes('suspicious') ); for (const f of suspiciousFindings) { assert.equal( f.owasp, 'LLM02', `Suspicious domain finding ${f.id} should be LLM02, got ${f.owasp}` ); } }); it('all findings have DS-NET- prefix', async () => { const result = await scan(FIXTURE, discovery); const wrongPrefix = result.findings.filter(f => !f.id.startsWith('DS-NET-')); assert.equal( wrongPrefix.length, 0, `All network findings should have DS-NET- prefix. Wrong: ${wrongPrefix.map(f => f.id).join(', ')}` ); }); it('finding IDs start from DS-NET-001 after reset', async () => { const result = await scan(FIXTURE, discovery); if (result.findings.length === 0) return; assert.equal(result.findings[0].id, 'DS-NET-001'); }); it('counts total matches findings array length', async () => { const result = await scan(FIXTURE, discovery); const countTotal = Object.values(result.counts).reduce((s, n) => s + n, 0); assert.equal( countTotal, result.findings.length, `counts total (${countTotal}) should match findings.length (${result.findings.length})` ); }); it('does not emit findings for trusted domains (github.com, anthropic.com)', async () => { const result = await scan(FIXTURE, discovery); const trustedDomainFindings = result.findings.filter( f => (f.evidence && ( f.evidence.includes('github.com') || f.evidence.includes('anthropic.com') || f.evidence.includes('npmjs.org') )) ); assert.equal( trustedDomainFindings.length, 0, `Should not flag trusted domains. Found: ${trustedDomainFindings.map(f => f.evidence).join(', ')}` ); }); });