From 6c6d11c354bc3641435fca47e0f0a387318b81d8 Mon Sep 17 00:00:00 2001 From: Ulrich Diedrichsen Date: Fri, 30 Jan 2026 10:36:48 +0100 Subject: [PATCH] feat(security): add intrusion detection system Add pattern-based intrusion detector with attack recognition for: - Brute force attacks (10 failures in 10min) - SSRF bypass attempts (3 attempts in 5min) - Path traversal attempts (5 attempts in 5min) - Port scanning (20 connections in 10sec) Features: - Event aggregation with sliding windows - Auto-blocking on detection - Configurable thresholds per pattern - Security event logging for all detections Co-Authored-By: Claude Sonnet 4.5 --- src/security/intrusion-detector.ts | 266 +++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 src/security/intrusion-detector.ts diff --git a/src/security/intrusion-detector.ts b/src/security/intrusion-detector.ts new file mode 100644 index 000000000..a19d73010 --- /dev/null +++ b/src/security/intrusion-detector.ts @@ -0,0 +1,266 @@ +/** + * Intrusion detection system + * Pattern-based attack detection with configurable thresholds + */ + +import { securityEventAggregator } from "./events/aggregator.js"; +import { securityLogger } from "./events/logger.js"; +import { SecurityActions, AttackPatterns, type SecurityEvent } from "./events/schema.js"; +import { ipManager } from "./ip-manager.js"; +import type { SecurityShieldConfig } from "../config/types.security.js"; + +export interface AttackPatternConfig { + threshold: number; + windowMs: number; +} + +export interface IntrusionDetectionResult { + detected: boolean; + pattern?: string; + count?: number; + threshold?: number; +} + +/** + * Intrusion detector + */ +export class IntrusionDetector { + private config: Required>; + + constructor(config?: SecurityShieldConfig["intrusionDetection"]) { + this.config = { + enabled: config?.enabled ?? true, + patterns: { + bruteForce: config?.patterns?.bruteForce ?? { threshold: 10, windowMs: 600_000 }, + ssrfBypass: config?.patterns?.ssrfBypass ?? { threshold: 3, windowMs: 300_000 }, + pathTraversal: config?.patterns?.pathTraversal ?? { threshold: 5, windowMs: 300_000 }, + portScanning: config?.patterns?.portScanning ?? { threshold: 20, windowMs: 10_000 }, + }, + anomalyDetection: config?.anomalyDetection ?? { + enabled: false, + learningPeriodMs: 86_400_000, + sensitivityScore: 0.95, + }, + }; + } + + /** + * Check for brute force attack pattern + */ + checkBruteForce(params: { + ip: string; + event: SecurityEvent; + }): IntrusionDetectionResult { + if (!this.config.enabled) { + return { detected: false }; + } + + const { ip, event } = params; + const pattern = this.config.patterns.bruteForce; + const key = `brute_force:${ip}`; + + const crossed = securityEventAggregator.trackEvent({ + key, + event, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }); + + if (crossed) { + const count = securityEventAggregator.getCount({ key, windowMs: pattern.windowMs }); + + // Log intrusion + securityLogger.logIntrusion({ + action: SecurityActions.BRUTE_FORCE_DETECTED, + ip, + resource: event.resource, + attackPattern: AttackPatterns.BRUTE_FORCE, + details: { + failedAttempts: count, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }, + }); + + // Auto-block if configured + this.autoBlock(ip, AttackPatterns.BRUTE_FORCE); + + return { + detected: true, + pattern: AttackPatterns.BRUTE_FORCE, + count, + threshold: pattern.threshold, + }; + } + + return { detected: false }; + } + + /** + * Check for SSRF bypass attempts + */ + checkSsrfBypass(params: { + ip: string; + event: SecurityEvent; + }): IntrusionDetectionResult { + if (!this.config.enabled) { + return { detected: false }; + } + + const { ip, event } = params; + const pattern = this.config.patterns.ssrfBypass; + const key = `ssrf_bypass:${ip}`; + + const crossed = securityEventAggregator.trackEvent({ + key, + event, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }); + + if (crossed) { + const count = securityEventAggregator.getCount({ key, windowMs: pattern.windowMs }); + + securityLogger.logIntrusion({ + action: SecurityActions.SSRF_BYPASS_ATTEMPT, + ip, + resource: event.resource, + attackPattern: AttackPatterns.SSRF_BYPASS, + details: { + attempts: count, + threshold: pattern.threshold, + }, + }); + + this.autoBlock(ip, AttackPatterns.SSRF_BYPASS); + + return { + detected: true, + pattern: AttackPatterns.SSRF_BYPASS, + count, + threshold: pattern.threshold, + }; + } + + return { detected: false }; + } + + /** + * Check for path traversal attempts + */ + checkPathTraversal(params: { + ip: string; + event: SecurityEvent; + }): IntrusionDetectionResult { + if (!this.config.enabled) { + return { detected: false }; + } + + const { ip, event } = params; + const pattern = this.config.patterns.pathTraversal; + const key = `path_traversal:${ip}`; + + const crossed = securityEventAggregator.trackEvent({ + key, + event, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }); + + if (crossed) { + const count = securityEventAggregator.getCount({ key, windowMs: pattern.windowMs }); + + securityLogger.logIntrusion({ + action: SecurityActions.PATH_TRAVERSAL_ATTEMPT, + ip, + resource: event.resource, + attackPattern: AttackPatterns.PATH_TRAVERSAL, + details: { + attempts: count, + threshold: pattern.threshold, + }, + }); + + this.autoBlock(ip, AttackPatterns.PATH_TRAVERSAL); + + return { + detected: true, + pattern: AttackPatterns.PATH_TRAVERSAL, + count, + threshold: pattern.threshold, + }; + } + + return { detected: false }; + } + + /** + * Check for port scanning + */ + checkPortScanning(params: { + ip: string; + event: SecurityEvent; + }): IntrusionDetectionResult { + if (!this.config.enabled) { + return { detected: false }; + } + + const { ip, event } = params; + const pattern = this.config.patterns.portScanning; + const key = `port_scan:${ip}`; + + const crossed = securityEventAggregator.trackEvent({ + key, + event, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }); + + if (crossed) { + const count = securityEventAggregator.getCount({ key, windowMs: pattern.windowMs }); + + securityLogger.logIntrusion({ + action: SecurityActions.PORT_SCANNING_DETECTED, + ip, + resource: event.resource, + attackPattern: AttackPatterns.PORT_SCANNING, + details: { + connections: count, + threshold: pattern.threshold, + windowMs: pattern.windowMs, + }, + }); + + this.autoBlock(ip, AttackPatterns.PORT_SCANNING); + + return { + detected: true, + pattern: AttackPatterns.PORT_SCANNING, + count, + threshold: pattern.threshold, + }; + } + + return { detected: false }; + } + + /** + * Auto-block IP if configured + */ + private autoBlock(ip: string, pattern: string): void { + // Use default 24h block duration + const durationMs = 86_400_000; // Will be configurable later + + ipManager.blockIp({ + ip, + reason: pattern, + durationMs, + source: "auto", + }); + } +} + +/** + * Singleton intrusion detector + */ +export const intrusionDetector = new IntrusionDetector();