/** * Threshold Manager * Configurable notification thresholds for security threat detection * CWE-778 Compliance: Logs all threshold configurations and evaluations */ const logger = require('./logger'); const logAggregator = require('./logAggregator'); const { db } = require('../database/db'); class ThresholdManager { constructor() { this.thresholds = new Map(); this.initialize(); } /** * Initialize threshold manager */ async initialize() { await this.createThresholdsTable(); await this.loadThresholds(); logger.info('[ThresholdManager] Initialized with configurable thresholds'); // Log initialization (CWE-778) logAggregator.aggregate('threshold_manager', 'info', 'security', 'Threshold manager initialized', { totalThresholds: this.thresholds.size }); } /** * Create thresholds table */ async createThresholdsTable() { return new Promise((resolve, reject) => { db.run(` CREATE TABLE IF NOT EXISTS security_thresholds ( id INTEGER PRIMARY KEY AUTOINCREMENT, threshold_id TEXT UNIQUE NOT NULL, name TEXT NOT NULL, description TEXT, pattern_type TEXT NOT NULL, metric_name TEXT NOT NULL, operator TEXT NOT NULL, threshold_value INTEGER NOT NULL, time_window_minutes INTEGER DEFAULT 30, severity TEXT NOT NULL, enabled INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ) `, async (err) => { if (err) reject(err); else { db.run(`CREATE INDEX IF NOT EXISTS idx_thresholds_pattern ON security_thresholds(pattern_type, enabled)`); db.run(`CREATE INDEX IF NOT EXISTS idx_thresholds_enabled ON security_thresholds(enabled)`); await this.createDefaultThresholds(); resolve(); } }); }); } /** * Create default thresholds for common security patterns */ async createDefaultThresholds() { const defaultThresholds = [ { threshold_id: 'THRESHOLD-BRUTE-FORCE', name: 'Brute Force Attack Threshold', description: 'Alert when failed login attempts exceed threshold', pattern_type: 'brute_force_attack', metric_name: 'failed_login_count', operator: '>=', threshold_value: 5, time_window_minutes: 10, severity: 'critical' }, { threshold_id: 'THRESHOLD-CREDENTIAL-STUFFING', name: 'Credential Stuffing Threshold', description: 'Alert on multiple username attempts from same IP', pattern_type: 'credential_stuffing', metric_name: 'unique_username_count', operator: '>=', threshold_value: 5, time_window_minutes: 5, severity: 'critical' }, { threshold_id: 'THRESHOLD-PRIVILEGE-ESC', name: 'Privilege Escalation Threshold', description: 'Alert on repeated unauthorized access attempts', pattern_type: 'privilege_escalation', metric_name: 'escalation_attempt_count', operator: '>=', threshold_value: 3, time_window_minutes: 30, severity: 'critical' }, { threshold_id: 'THRESHOLD-SUSPICIOUS-IP', name: 'Suspicious IP Activity Threshold', description: 'Alert on excessive requests from single IP', pattern_type: 'suspicious_ip', metric_name: 'request_count', operator: '>=', threshold_value: 100, time_window_minutes: 15, severity: 'high' }, { threshold_id: 'THRESHOLD-DATA-EXFIL', name: 'Data Exfiltration Threshold', description: 'Alert on excessive data downloads', pattern_type: 'data_exfiltration', metric_name: 'download_count', operator: '>=', threshold_value: 10, time_window_minutes: 60, severity: 'high' }, { threshold_id: 'THRESHOLD-SESSION-ANOMALY', name: 'Session Anomaly Threshold', description: 'Alert on unusual session patterns', pattern_type: 'session_anomaly', metric_name: 'anomaly_score', operator: '>=', threshold_value: 70, time_window_minutes: 30, severity: 'medium' }, { threshold_id: 'THRESHOLD-IMPOSSIBLE-TRAVEL', name: 'Impossible Travel Threshold', description: 'Alert on geographically impossible travel speed', pattern_type: 'impossible_travel', metric_name: 'travel_speed_kmh', operator: '>=', threshold_value: 800, time_window_minutes: 60, severity: 'high' }, { threshold_id: 'THRESHOLD-THREAT-SCORE', name: 'Critical Threat Score Threshold', description: 'Alert when overall threat score is critical', pattern_type: 'threat_score', metric_name: 'threat_score', operator: '>=', threshold_value: 80, time_window_minutes: 60, severity: 'critical' } ]; for (const threshold of defaultThresholds) { await new Promise((resolve, reject) => { db.run( `INSERT OR IGNORE INTO security_thresholds (threshold_id, name, description, pattern_type, metric_name, operator, threshold_value, time_window_minutes, severity) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ threshold.threshold_id, threshold.name, threshold.description, threshold.pattern_type, threshold.metric_name, threshold.operator, threshold.threshold_value, threshold.time_window_minutes, threshold.severity ], (err) => { if (err) reject(err); else resolve(); } ); }); } logger.info(`[ThresholdManager] Created ${defaultThresholds.length} default thresholds`); } /** * Load thresholds from database into memory */ async loadThresholds() { return new Promise((resolve, reject) => { db.all( `SELECT * FROM security_thresholds WHERE enabled = 1`, [], (err, rows) => { if (err) { reject(err); } else { this.thresholds.clear(); rows.forEach(row => { this.thresholds.set(row.threshold_id, row); }); logger.info(`[ThresholdManager] Loaded ${rows.length} active thresholds`); resolve(); } } ); }); } /** * Evaluate if a metric value exceeds threshold * CWE-778: Logs all threshold evaluations */ async evaluateThreshold(patternType, metricName, value, context = {}) { const matchingThresholds = Array.from(this.thresholds.values()).filter( t => t.pattern_type === patternType && t.metric_name === metricName ); if (matchingThresholds.length === 0) { return { exceeded: false, thresholds: [] }; } const exceededThresholds = []; for (const threshold of matchingThresholds) { const exceeded = this.compareValue(value, threshold.operator, threshold.threshold_value); // Log threshold evaluation (CWE-778) logAggregator.aggregate('threshold_manager', 'info', 'security', 'Threshold evaluated', { thresholdId: threshold.threshold_id, patternType, metricName, value, operator: threshold.operator, thresholdValue: threshold.threshold_value, exceeded, severity: threshold.severity, context }); if (exceeded) { exceededThresholds.push({ ...threshold, actualValue: value, context }); logger.warn(`[ThresholdManager] Threshold exceeded: ${threshold.name} (${value} ${threshold.operator} ${threshold.threshold_value})`); } } return { exceeded: exceededThresholds.length > 0, thresholds: exceededThresholds }; } /** * Compare value against threshold using operator */ compareValue(value, operator, threshold) { switch (operator) { case '>=': return value >= threshold; case '>': return value > threshold; case '<=': return value <= threshold; case '<': return value < threshold; case '==': return value == threshold; case '!=': return value != threshold; default: return false; } } /** * Get all thresholds */ async getThresholds(filters = {}) { const { patternType, enabled, limit = 100 } = filters; let whereClause = []; let params = []; if (patternType) { whereClause.push('pattern_type = ?'); params.push(patternType); } if (enabled !== undefined) { whereClause.push('enabled = ?'); params.push(enabled ? 1 : 0); } const where = whereClause.length > 0 ? `WHERE ${whereClause.join(' AND ')}` : ''; params.push(limit); return new Promise((resolve, reject) => { db.all( `SELECT * FROM security_thresholds ${where} ORDER BY pattern_type, threshold_value DESC LIMIT ?`, params, (err, rows) => { if (err) reject(err); else resolve(rows); } ); }); } /** * Get threshold by ID */ async getThresholdById(thresholdId) { return new Promise((resolve, reject) => { db.get( `SELECT * FROM security_thresholds WHERE threshold_id = ?`, [thresholdId], (err, row) => { if (err) reject(err); else resolve(row); } ); }); } /** * Create new threshold * CWE-778: Logs threshold creation */ async createThreshold(data, userId) { const thresholdId = `THRESHOLD-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`; return new Promise((resolve, reject) => { db.run( `INSERT INTO security_thresholds (threshold_id, name, description, pattern_type, metric_name, operator, threshold_value, time_window_minutes, severity, enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ thresholdId, data.name, data.description || '', data.pattern_type, data.metric_name, data.operator, data.threshold_value, data.time_window_minutes || 30, data.severity, data.enabled !== undefined ? (data.enabled ? 1 : 0) : 1 ], async (err) => { if (err) { reject(err); } else { await this.loadThresholds(); // Log threshold creation (CWE-778) logAggregator.aggregate('threshold_manager', 'info', 'security', 'Threshold created', { thresholdId, userId, name: data.name, patternType: data.pattern_type, metricName: data.metric_name, thresholdValue: data.threshold_value, severity: data.severity }); logger.info(`[ThresholdManager] Threshold created: ${thresholdId} by user ${userId}`); resolve({ thresholdId }); } } ); }); } /** * Update threshold * CWE-778: Logs threshold modifications */ async updateThreshold(thresholdId, updates, userId) { const allowedFields = ['name', 'description', 'operator', 'threshold_value', 'time_window_minutes', 'severity', 'enabled']; const setClause = []; const params = []; for (const [key, value] of Object.entries(updates)) { if (allowedFields.includes(key)) { setClause.push(`${key} = ?`); params.push(key === 'enabled' ? (value ? 1 : 0) : value); } } if (setClause.length === 0) { throw new Error('No valid fields to update'); } setClause.push('updated_at = CURRENT_TIMESTAMP'); params.push(thresholdId); return new Promise((resolve, reject) => { db.run( `UPDATE security_thresholds SET ${setClause.join(', ')} WHERE threshold_id = ?`, params, async (err) => { if (err) { reject(err); } else { await this.loadThresholds(); // Log threshold update (CWE-778) logAggregator.aggregate('threshold_manager', 'info', 'security', 'Threshold updated', { thresholdId, userId, updates }); logger.info(`[ThresholdManager] Threshold updated: ${thresholdId} by user ${userId}`); resolve({ success: true }); } } ); }); } /** * Delete threshold * CWE-778: Logs threshold deletion */ async deleteThreshold(thresholdId, userId) { return new Promise((resolve, reject) => { db.run( `DELETE FROM security_thresholds WHERE threshold_id = ?`, [thresholdId], async (err) => { if (err) { reject(err); } else { await this.loadThresholds(); // Log threshold deletion (CWE-778) logAggregator.aggregate('threshold_manager', 'warn', 'security', 'Threshold deleted', { thresholdId, userId }); logger.info(`[ThresholdManager] Threshold deleted: ${thresholdId} by user ${userId}`); resolve({ success: true }); } } ); }); } /** * Get threshold statistics */ async getStatistics() { return new Promise((resolve, reject) => { db.get( `SELECT COUNT(*) as total, SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) as enabled, SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END) as disabled, COUNT(DISTINCT pattern_type) as unique_patterns, COUNT(DISTINCT severity) as unique_severities FROM security_thresholds`, [], (err, row) => { if (err) reject(err); else resolve(row); } ); }); } } // Create singleton instance const thresholdManager = new ThresholdManager(); module.exports = thresholdManager;