/** * @module @arcis/node/middleware/correlation * * V1.6 / improvements.md §1.3 — Stateful per-IP correlation window. * * Today's middleware is stateless: each request is judged on its own. * That misses three classes of attack: * * - Scanner sweep. One IP firing payloads from every category in * quick succession is a scanner, not a real user. * - Credential stuffing. Same login route, same IP, dozens of * distinct usernames in 60 seconds. * - Race-condition probe. POST /a immediately followed by GET /b * from the same IP, within 200 ms. Either is fine alone. * * This module records a small rolling event log per IP (capped) and * exposes detection helpers. Detection is *additive* — Pattern 4 * applies (fail-open). If something goes wrong here, the existing * rate-limit + per-vector defenses still run. * * Mirrors `arcis-python/arcis/middleware/correlation.py`. Both SDKs * must accept the same base corpus per Pattern 7. */ export interface CorrelationEvent { /** Wall-clock seconds (Date.now() / 1000) or test-injected value. */ timestamp: number; /** Detector or operation that produced this event. */ vector: string; route: string; method: string; /** Username / email / token bucket if relevant; otherwise undefined. */ distinctValue?: string; } export interface CorrelationDetections { scanner: boolean; credentialStuffing: boolean; raceWindow: boolean; distinctVectors: number; distinctValues: number; requestsInWindow: number; } export interface CorrelationWindowOptions { windowSeconds?: number; maxIps?: number; maxEventsPerIp?: number; scannerDistinctVectors?: number; scannerMinRequests?: number; credentialStuffingDistinctValues?: number; raceWindowMs?: number; /** Pre-registered race-pair routes; ad-hoc detect_race_window also works. */ racePairs?: Array<[string, string]>; } /** * Rolling per-IP correlation window. Mirrors the Python * `CorrelationWindow` class. * * All detection helpers are read-only; only `record` mutates state. */ export declare class CorrelationWindow { private readonly windowSeconds; private readonly maxIps; private readonly maxEventsPerIp; private readonly scannerDistinctVectors; private readonly scannerMinRequests; private readonly csDistinctValues; private readonly raceWindowSeconds; private readonly racePairKeys; private readonly racePairTuples; private readonly buckets; constructor(options?: CorrelationWindowOptions); record(ip: string, vector: string, route: string, method?: string, distinctValue?: string, now?: number): CorrelationDetections; detectScanner(ip: string, now?: number): boolean; detectCredentialStuffing(ip: string, route: string, now?: number): boolean; detectRaceWindow(ip: string, routePair: [string, string], now?: number): boolean; reset(ip?: string): void; stats(): { trackedIps: number; eventsInWindow: number; }; private evictStale; private evaluate; private isScanner; private isCredentialStuffing; private racePairInBucket; private isRaceAny; } //# sourceMappingURL=correlation.d.ts.map