@aikidosec/firewall
Version:
Zen by Aikido is an embedded Application Firewall that autonomously protects Node.js apps against common and critical attacks, provides rate limiting, detects malicious traffic (including bots), and more.
204 lines (203 loc) • 6.74 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.operations = {};
this.sqlTokenizationFailures = 0;
this.requests = {
total: 0,
aborted: 0,
rateLimited: 0,
attacksDetected: { total: 0, blocked: 0 },
attackWaves: {
total: 0,
blocked: 0,
},
};
this.userAgents = {
breakdown: {},
};
this.ipAddresses = {
breakdown: {},
};
this.maxPerfSamplesInMemory = maxPerfSamplesInMemory;
this.maxCompressedStatsInMemory = maxCompressedStatsInMemory;
}
hasCompressedStats() {
return Object.values(this.operations).some((sinkStats) => sinkStats.compressedTimings.length > 0);
}
isEmpty() {
return (this.requests.total === 0 &&
Object.keys(this.operations).length === 0 &&
this.requests.attacksDetected.total === 0);
}
reset() {
this.operations = {};
this.requests = {
total: 0,
aborted: 0,
rateLimited: 0,
attacksDetected: { total: 0, blocked: 0 },
attackWaves: {
total: 0,
blocked: 0,
},
};
this.userAgents = {
breakdown: {},
};
this.ipAddresses = {
breakdown: {},
};
this.startedAt = Date.now();
this.sqlTokenizationFailures = 0;
}
getStats() {
const operations = {};
for (const operation in this.operations) {
const operationStats = this.operations[operation];
operations[operation] = {
kind: operationStats.kind,
total: operationStats.total,
attacksDetected: {
total: operationStats.attacksDetected.total,
blocked: operationStats.attacksDetected.blocked,
},
interceptorThrewError: operationStats.interceptorThrewError,
withoutContext: operationStats.withoutContext,
compressedTimings: operationStats.compressedTimings,
};
}
return {
operations: operations,
startedAt: this.startedAt,
sqlTokenizationFailures: this.sqlTokenizationFailures,
requests: this.requests,
userAgents: this.userAgents,
ipAddresses: this.ipAddresses,
};
}
ensureOperationStats(operation, kind) {
if (!this.operations[operation]) {
this.operations[operation] = {
withoutContext: 0,
kind: kind,
total: 0,
durations: [],
compressedTimings: [],
interceptorThrewError: 0,
attacksDetected: {
total: 0,
blocked: 0,
},
};
}
}
compressPerfSamples(operation) {
if (operation.length === 0) {
return;
}
/* c8 ignore start */
if (!this.operations[operation]) {
return;
}
if (this.operations[operation].durations.length === 0) {
return;
}
/* c8 ignore stop */
const timings = this.operations[operation].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.operations[operation].compressedTimings.push({
averageInMS,
percentiles: {
"50": p50,
"75": p75,
"90": p90,
"95": p95,
"99": p99,
},
compressedAt: Date.now(),
});
if (this.operations[operation].compressedTimings.length >
this.maxCompressedStatsInMemory) {
this.operations[operation].compressedTimings.shift();
}
this.operations[operation].durations = [];
}
interceptorThrewError(operation, kind) {
if (operation.length === 0) {
return;
}
this.ensureOperationStats(operation, kind);
this.operations[operation].total += 1;
this.operations[operation].interceptorThrewError += 1;
}
onDetectedAttack({ blocked }) {
this.requests.attacksDetected.total += 1;
if (blocked) {
this.requests.attacksDetected.blocked += 1;
}
}
onIPAddressMatches(matches) {
matches.forEach((key) => {
if (!this.ipAddresses.breakdown[key]) {
this.ipAddresses.breakdown[key] = 0;
}
this.ipAddresses.breakdown[key] += 1;
});
}
onUserAgentMatches(matches) {
matches.forEach((key) => {
if (!this.userAgents.breakdown[key]) {
this.userAgents.breakdown[key] = 0;
}
this.userAgents.breakdown[key] += 1;
});
}
onAbortedRequest() {
this.requests.aborted += 1;
}
onRequest() {
this.requests.total += 1;
}
onRateLimitedRequest() {
this.requests.rateLimited += 1;
}
onAttackWaveDetected() {
this.requests.attackWaves.total += 1;
}
onInspectedCall({ operation, kind, blocked, attackDetected, durationInMs, withoutContext, }) {
if (operation.length === 0) {
return;
}
this.ensureOperationStats(operation, kind);
this.operations[operation].total += 1;
if (withoutContext) {
this.operations[operation].withoutContext += 1;
return;
}
if (this.operations[operation].durations.length >= this.maxPerfSamplesInMemory) {
this.compressPerfSamples(operation);
}
this.operations[operation].durations.push(durationInMs);
if (attackDetected) {
this.operations[operation].attacksDetected.total += 1;
if (blocked) {
this.operations[operation].attacksDetected.blocked += 1;
}
}
}
forceCompress() {
for (const kind in this.operations) {
this.compressPerfSamples(kind);
}
}
onSqlTokenizationFailure() {
this.sqlTokenizationFailures += 1;
}
}
exports.InspectionStatistics = InspectionStatistics;