Files
Autonomous-Bug-Explorer/dist/core/AnomalyDetector.js

138 lines
5.3 KiB
JavaScript

"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('<script>alert(1)</script>'))
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;