@aikidosec/firewall
Version:
Zen by Aikido is an embedded Web Application Firewall that autonomously protects Node.js apps against common and critical attacks
137 lines (136 loc) • 4.57 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InspectionStatistics = void 0;
const percentiles_1 = require("../helpers/percentiles");
class InspectionStatistics {
constructor({ maxPerfSamplesInMemory, maxCompressedStatsInMemory, }) {
this.startedAt = Date.now();
this.stats = {};
this.requests = { total: 0, aborted: 0, attacksDetected: { total: 0, blocked: 0 } };
this.maxPerfSamplesInMemory = maxPerfSamplesInMemory;
this.maxCompressedStatsInMemory = maxCompressedStatsInMemory;
}
hasCompressedStats() {
return Object.values(this.stats).some((sinkStats) => sinkStats.compressedTimings.length > 0);
}
isEmpty() {
return (this.requests.total === 0 &&
Object.keys(this.stats).length === 0 &&
this.requests.attacksDetected.total === 0);
}
reset() {
this.stats = {};
this.requests = {
total: 0,
aborted: 0,
attacksDetected: { total: 0, blocked: 0 },
};
this.startedAt = Date.now();
}
getStats() {
const sinks = {};
for (const sink in this.stats) {
const sinkStats = this.stats[sink];
sinks[sink] = {
total: sinkStats.total,
attacksDetected: {
total: sinkStats.attacksDetected.total,
blocked: sinkStats.attacksDetected.blocked,
},
interceptorThrewError: sinkStats.interceptorThrewError,
withoutContext: sinkStats.withoutContext,
compressedTimings: sinkStats.compressedTimings,
};
}
return {
sinks: sinks,
startedAt: this.startedAt,
requests: this.requests,
};
}
ensureSinkStats(sink) {
if (!this.stats[sink]) {
this.stats[sink] = {
withoutContext: 0,
total: 0,
durations: [],
compressedTimings: [],
interceptorThrewError: 0,
attacksDetected: {
total: 0,
blocked: 0,
},
};
}
}
compressPerfSamples(sink) {
/* c8 ignore start */
if (!this.stats[sink]) {
return;
}
if (this.stats[sink].durations.length === 0) {
return;
}
/* c8 ignore stop */
const timings = this.stats[sink].durations;
const averageInMS = timings.reduce((acc, curr) => acc + curr, 0) / timings.length;
const [p50, p75, p90, p95, p99] = (0, percentiles_1.percentiles)([50, 75, 90, 95, 99], timings);
this.stats[sink].compressedTimings.push({
averageInMS,
percentiles: {
"50": p50,
"75": p75,
"90": p90,
"95": p95,
"99": p99,
},
compressedAt: Date.now(),
});
if (this.stats[sink].compressedTimings.length >
this.maxCompressedStatsInMemory) {
this.stats[sink].compressedTimings.shift();
}
this.stats[sink].durations = [];
}
interceptorThrewError(sink) {
this.ensureSinkStats(sink);
this.stats[sink].total += 1;
this.stats[sink].interceptorThrewError += 1;
}
onDetectedAttack({ blocked }) {
this.requests.attacksDetected.total += 1;
if (blocked) {
this.requests.attacksDetected.blocked += 1;
}
}
onAbortedRequest() {
this.requests.aborted += 1;
}
onRequest() {
this.requests.total += 1;
}
onInspectedCall({ sink, blocked, attackDetected, durationInMs, withoutContext, }) {
this.ensureSinkStats(sink);
this.stats[sink].total += 1;
if (withoutContext) {
this.stats[sink].withoutContext += 1;
return;
}
if (this.stats[sink].durations.length >= this.maxPerfSamplesInMemory) {
this.compressPerfSamples(sink);
}
this.stats[sink].durations.push(durationInMs);
if (attackDetected) {
this.stats[sink].attacksDetected.total += 1;
if (blocked) {
this.stats[sink].attacksDetected.blocked += 1;
}
}
}
forceCompress() {
for (const sink in this.stats) {
this.compressPerfSamples(sink);
}
}
}
exports.InspectionStatistics = InspectionStatistics;