secure-kit
Version:
Production-grade security + performance toolkit for backend frameworks with OWASP Top 10 compliance
335 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SecurityMonitor = exports.SecurityEventType = void 0;
const events_1 = require("events");
var SecurityEventType;
(function (SecurityEventType) {
SecurityEventType["RATE_LIMIT_EXCEEDED"] = "rate_limit_exceeded";
SecurityEventType["SUSPICIOUS_INPUT"] = "suspicious_input";
SecurityEventType["AUTHENTICATION_FAILURE"] = "authentication_failure";
SecurityEventType["UNAUTHORIZED_ACCESS"] = "unauthorized_access";
SecurityEventType["SQL_INJECTION_ATTEMPT"] = "sql_injection_attempt";
SecurityEventType["XSS_ATTEMPT"] = "xss_attempt";
SecurityEventType["CSRF_TOKEN_MISMATCH"] = "csrf_token_mismatch";
SecurityEventType["FILE_UPLOAD_VIOLATION"] = "file_upload_violation";
SecurityEventType["SECURITY_HEADER_MISSING"] = "security_header_missing";
SecurityEventType["MALFORMED_REQUEST"] = "malformed_request";
SecurityEventType["BRUTE_FORCE_ATTEMPT"] = "brute_force_attempt";
SecurityEventType["ANOMALOUS_BEHAVIOR"] = "anomalous_behavior";
})(SecurityEventType || (exports.SecurityEventType = SecurityEventType = {}));
class SecurityMonitor extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.events = [];
this.threatRules = new Map();
this.lastRuleTrigger = new Map();
this.maxEventsHistory = config.maxEventsHistory || 10000;
this.metrics = this.initializeMetrics();
// Load default threat detection rules
this.loadDefaultThreatRules();
// Load custom rules if provided
if (config.threatDetectionRules) {
config.threatDetectionRules.forEach(rule => this.addThreatRule(rule));
}
}
/**
* Record a security event
*/
recordEvent(event) {
const fullEvent = {
id: this.generateEventId(),
timestamp: Date.now(),
...event,
};
// Add to events history
this.events.push(fullEvent);
// Maintain history size limit
if (this.events.length > this.maxEventsHistory) {
this.events.shift();
}
// Update metrics
this.updateMetrics(fullEvent);
// Check threat detection rules
this.checkThreatRules(fullEvent);
// Emit event for external listeners
this.emit('securityEvent', fullEvent);
return fullEvent;
}
/**
* Get current security metrics
*/
getMetrics() {
return { ...this.metrics };
}
/**
* Get recent security events
*/
getRecentEvents(limit = 100) {
return this.events.slice(-limit);
}
/**
* Get events by type
*/
getEventsByType(type, limit = 100) {
return this.events.filter(event => event.type === type).slice(-limit);
}
/**
* Get events by severity
*/
getEventsBySeverity(severity, limit = 100) {
return this.events
.filter(event => event.severity === severity)
.slice(-limit);
}
/**
* Get events in time range
*/
getEventsInRange(startTime, endTime) {
return this.events.filter(event => event.timestamp >= startTime && event.timestamp <= endTime);
}
/**
* Add a threat detection rule
*/
addThreatRule(rule) {
this.threatRules.set(rule.id, rule);
}
/**
* Remove a threat detection rule
*/
removeThreatRule(ruleId) {
this.threatRules.delete(ruleId);
this.lastRuleTrigger.delete(ruleId);
}
/**
* Check if IP/source is currently exhibiting suspicious behavior
*/
isSuspiciousSource(source, timeWindow = 300000) {
const cutoff = Date.now() - timeWindow;
const recentEvents = this.events.filter(event => event.source === source &&
event.timestamp > cutoff &&
(event.severity === 'high' || event.severity === 'critical'));
return recentEvents.length > 5; // Threshold for suspicious behavior
}
/**
* Generate security report for a time period
*/
generateReport(startTime, endTime) {
const events = this.getEventsInRange(startTime, endTime);
const summary = {
totalEvents: events.length,
criticalEvents: events.filter(e => e.severity === 'critical').length,
highSeverityEvents: events.filter(e => e.severity === 'high').length,
uniqueSources: new Set(events.map(e => e.source)).size,
};
const threatCounts = new Map();
const sourceCounts = new Map();
events.forEach(event => {
threatCounts.set(event.type, (threatCounts.get(event.type) || 0) + 1);
sourceCounts.set(event.source, (sourceCounts.get(event.source) || 0) + 1);
});
const topThreats = Array.from(threatCounts.entries())
.map(([type, count]) => ({ type, count }))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
const suspiciousSources = Array.from(sourceCounts.entries())
.filter(([_, count]) => count > 10)
.map(([source, _]) => source);
const recommendations = this.generateRecommendations(events);
return {
summary,
topThreats,
suspiciousSources,
recommendations,
};
}
/**
* Clear all events and reset metrics
*/
clear() {
this.events = [];
this.metrics = this.initializeMetrics();
this.lastRuleTrigger.clear();
}
initializeMetrics() {
return {
totalEvents: 0,
eventsByType: Object.values(SecurityEventType).reduce((acc, type) => {
acc[type] = 0;
return acc;
}, {}),
eventsBySeverity: {
low: 0,
medium: 0,
high: 0,
critical: 0,
},
recentEvents: [],
topSources: [],
alertThresholds: {
rateLimit: 100,
authFailures: 10,
injectionAttempts: 5,
},
};
}
updateMetrics(event) {
this.metrics.totalEvents++;
this.metrics.eventsByType[event.type]++;
this.metrics.eventsBySeverity[event.severity]++;
// Update recent events (last 10)
this.metrics.recentEvents.push(event);
if (this.metrics.recentEvents.length > 10) {
this.metrics.recentEvents.shift();
}
// Update top sources
this.updateTopSources(event.source);
}
updateTopSources(source) {
const existing = this.metrics.topSources.find(s => s.source === source);
if (existing) {
existing.count++;
}
else {
this.metrics.topSources.push({ source, count: 1 });
}
// Sort and keep top 10
this.metrics.topSources.sort((a, b) => b.count - a.count);
this.metrics.topSources = this.metrics.topSources.slice(0, 10);
}
generateEventId() {
return `sec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
checkThreatRules(event) {
for (const [ruleId, rule] of this.threatRules) {
if (!rule.eventTypes.includes(event.type))
continue;
// Check cooldown
const lastTrigger = this.lastRuleTrigger.get(ruleId);
if (lastTrigger && Date.now() - lastTrigger < rule.cooldown) {
continue;
}
// Check rule condition
if (rule.condition(this.events)) {
this.lastRuleTrigger.set(ruleId, Date.now());
this.handleThreatDetection(rule, event);
}
}
}
handleThreatDetection(rule, triggerEvent) {
const threatEvent = {
id: this.generateEventId(),
timestamp: Date.now(),
type: SecurityEventType.ANOMALOUS_BEHAVIOR,
severity: rule.severity,
source: triggerEvent.source,
details: {
ruleName: rule.name,
ruleDescription: rule.description,
triggerEvent: triggerEvent.id,
action: rule.action,
},
metadata: triggerEvent.metadata,
};
this.events.push(threatEvent);
this.updateMetrics(threatEvent);
// Emit threat detection event
this.emit('threatDetected', {
rule,
triggerEvent,
threatEvent,
});
// Take action based on rule
switch (rule.action) {
case 'block':
this.emit('blockRequest', { source: triggerEvent.source, rule });
break;
case 'alert':
this.emit('securityAlert', { rule, triggerEvent });
break;
case 'log':
// Already logged by adding to events
break;
}
}
loadDefaultThreatRules() {
const defaultRules = [
{
id: 'brute_force_detection',
name: 'Brute Force Attack Detection',
description: 'Detects multiple authentication failures from the same source',
eventTypes: [SecurityEventType.AUTHENTICATION_FAILURE],
condition: events => {
const recent = events.filter(e => e.type === SecurityEventType.AUTHENTICATION_FAILURE &&
Date.now() - e.timestamp < 300000 // 5 minutes
);
const sourceGroups = new Map();
recent.forEach(e => {
sourceGroups.set(e.source, (sourceGroups.get(e.source) || 0) + 1);
});
return Array.from(sourceGroups.values()).some(count => count >= 5);
},
action: 'block',
severity: 'high',
cooldown: 600000, // 10 minutes
},
{
id: 'injection_attack_pattern',
name: 'Injection Attack Pattern',
description: 'Detects patterns of SQL/NoSQL injection attempts',
eventTypes: [SecurityEventType.SQL_INJECTION_ATTEMPT],
condition: events => {
const recent = events.filter(e => e.type === SecurityEventType.SQL_INJECTION_ATTEMPT &&
Date.now() - e.timestamp < 600000 // 10 minutes
);
return recent.length >= 3;
},
action: 'alert',
severity: 'high',
cooldown: 300000, // 5 minutes
},
{
id: 'rate_limit_abuse',
name: 'Rate Limit Abuse',
description: 'Detects persistent rate limit violations',
eventTypes: [SecurityEventType.RATE_LIMIT_EXCEEDED],
condition: events => {
const recent = events.filter(e => e.type === SecurityEventType.RATE_LIMIT_EXCEEDED &&
Date.now() - e.timestamp < 3600000 // 1 hour
);
const sourceGroups = new Map();
recent.forEach(e => {
sourceGroups.set(e.source, (sourceGroups.get(e.source) || 0) + 1);
});
return Array.from(sourceGroups.values()).some(count => count >= 10);
},
action: 'block',
severity: 'medium',
cooldown: 1800000, // 30 minutes
},
];
defaultRules.forEach(rule => this.addThreatRule(rule));
}
generateRecommendations(events) {
const recommendations = [];
const criticalCount = events.filter(e => e.severity === 'critical').length;
const highCount = events.filter(e => e.severity === 'high').length;
if (criticalCount > 0) {
recommendations.push(`${criticalCount} critical security events detected - immediate action required`);
}
if (highCount > 10) {
recommendations.push('High volume of high-severity events - consider tightening security policies');
}
const injectionAttempts = events.filter(e => e.type === SecurityEventType.SQL_INJECTION_ATTEMPT ||
e.type === SecurityEventType.XSS_ATTEMPT).length;
if (injectionAttempts > 5) {
recommendations.push('Multiple injection attempts detected - review input validation and sanitization');
}
const authFailures = events.filter(e => e.type === SecurityEventType.AUTHENTICATION_FAILURE).length;
if (authFailures > 20) {
recommendations.push('High number of authentication failures - consider implementing account lockout policies');
}
return recommendations;
}
}
exports.SecurityMonitor = SecurityMonitor;
//# sourceMappingURL=security-monitor.js.map