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 <noreply@anthropic.com>
This commit is contained in:
parent
73ce95d9cc
commit
6c6d11c354
266
src/security/intrusion-detector.ts
Normal file
266
src/security/intrusion-detector.ts
Normal file
@ -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<NonNullable<SecurityShieldConfig["intrusionDetection"]>>;
|
||||||
|
|
||||||
|
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();
|
||||||
Loading…
Reference in New Issue
Block a user