"use strict"; /** * AnomalyDetector — heuristic rules to detect anomalies from observations. * Each rule is independent and testable in isolation. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.AnomalyDetector = void 0; let anomalyCounter = 0; function makeId() { anomalyCounter += 1; return `anom_${Date.now()}_${anomalyCounter.toString().padStart(4, '0')}`; } class AnomalyDetector { detect(observation, actionTrace) { const anomalies = []; const httpAnomaly = this.checkHttpErrors(observation, actionTrace); if (httpAnomaly) anomalies.push(httpAnomaly); const jsAnomaly = this.checkJsExceptions(observation, actionTrace); if (jsAnomaly) anomalies.push(jsAnomaly); const consoleAnomaly = this.checkConsoleErrors(observation, actionTrace); if (consoleAnomaly) anomalies.push(consoleAnomaly); return anomalies; } /** Rule: HTTP 4xx or 5xx responses */ checkHttpErrors(observation, actionTrace) { const errorResponses = observation.httpResponses.filter((r) => r.status >= 400); if (errorResponses.length === 0) return null; const hasServerError = errorResponses.some((r) => r.status >= 500); const severity = hasServerError ? 'high' : 'medium'; const statusCodes = errorResponses.map((r) => r.status).join(', '); return this.buildAnomaly({ type: 'http_error', severity, observationId: observation.id, actionTrace, description: `HTTP error responses detected: ${statusCodes}`, evidence: { httpLog: errorResponses, rawErrors: errorResponses.map((r) => `${r.method} ${r.url} → ${r.status} (${r.durationMs}ms)`), }, }); } /** Rule: uncaught JS exceptions */ checkJsExceptions(observation, actionTrace) { if (observation.jsExceptions.length === 0) return null; return this.buildAnomaly({ type: 'js_exception', severity: 'high', observationId: observation.id, actionTrace, description: `Uncaught JS exception: ${observation.jsExceptions[0]}`, evidence: { rawErrors: observation.jsExceptions, }, }); } /** Rule: console.error messages */ checkConsoleErrors(observation, actionTrace) { if (observation.consoleErrors.length === 0) return null; return this.buildAnomaly({ type: 'console_error', severity: 'low', observationId: observation.id, actionTrace, description: `Console error detected: ${observation.consoleErrors[0]}`, evidence: { rawErrors: observation.consoleErrors, }, }); } /** * Rule: server accepted clearly invalid/empty fuzz input (got 2xx). * fuzzedValue is the value that was submitted; responseStatus is the HTTP response. */ checkValidationBypass(observation, actionTrace, fuzzedValue) { const has2xx = observation.httpResponses.some((r) => r.status >= 200 && r.status < 300); if (!has2xx) return null; return this.buildAnomaly({ type: 'validation_bypass', severity: 'high', observationId: observation.id, actionTrace, description: `Server accepted invalid input without error (value: ${JSON.stringify(fuzzedValue).substring(0, 50)})`, evidence: { httpLog: observation.httpResponses, rawErrors: [`Fuzzed with: ${fuzzedValue}`] }, }); } /** Rule: server returned 500 on a fuzzed input */ checkServerErrorOnFuzz(observation, actionTrace) { const has5xx = observation.httpResponses.some((r) => r.status >= 500); if (!has5xx) return null; return this.buildAnomaly({ type: 'server_error_on_fuzz', severity: 'high', observationId: observation.id, actionTrace, description: 'Server returned 5xx on fuzzed input', evidence: { httpLog: observation.httpResponses.filter((r) => r.status >= 500), rawErrors: observation.jsExceptions, }, }); } /** Rule: fuzzed script tag appears in response body (XSS reflection) */ checkXssReflection(observation, actionTrace, domSnapshot) { if (!domSnapshot.includes('')) return null; return this.buildAnomaly({ type: 'xss_reflection', severity: 'critical', observationId: observation.id, actionTrace, description: 'XSS reflection detected: fuzzed script tag appeared in DOM', evidence: { rawErrors: ['XSS payload reflected in DOM'] }, }); } buildAnomaly(params) { return { id: makeId(), type: params.type, severity: params.severity, observationId: params.observationId, actionTrace: params.actionTrace, description: params.description, evidence: params.evidence, timestamp: Date.now(), }; } } exports.AnomalyDetector = AnomalyDetector;