UNPKG

@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
"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;