import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { logger } from '../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from './classes.securitylogger.js'; import { RustSecurityBridge } from './classes.rustsecuritybridge.js'; import { LRUCache } from 'lru-cache'; /** * Reputation threshold scores */ export var ReputationThreshold; (function (ReputationThreshold) { ReputationThreshold[ReputationThreshold["HIGH_RISK"] = 20] = "HIGH_RISK"; ReputationThreshold[ReputationThreshold["MEDIUM_RISK"] = 50] = "MEDIUM_RISK"; ReputationThreshold[ReputationThreshold["LOW_RISK"] = 80] = "LOW_RISK"; // Score below this is considered low risk (but not trusted) })(ReputationThreshold || (ReputationThreshold = {})); /** * IP type classifications */ export var IPType; (function (IPType) { IPType["RESIDENTIAL"] = "residential"; IPType["DATACENTER"] = "datacenter"; IPType["PROXY"] = "proxy"; IPType["TOR"] = "tor"; IPType["VPN"] = "vpn"; IPType["UNKNOWN"] = "unknown"; })(IPType || (IPType = {})); /** * Class for checking IP reputation of inbound email senders */ export class IPReputationChecker { static instance; reputationCache; options; storageManager; // StorageManager instance // Default DNSBL servers static DEFAULT_DNSBL_SERVERS = [ 'zen.spamhaus.org', // Spamhaus 'bl.spamcop.net', // SpamCop 'b.barracudacentral.org', // Barracuda 'spam.dnsbl.sorbs.net', // SORBS 'dnsbl.sorbs.net', // SORBS (expanded) 'cbl.abuseat.org', // Composite Blocking List 'xbl.spamhaus.org', // Spamhaus XBL 'pbl.spamhaus.org', // Spamhaus PBL 'dnsbl-1.uceprotect.net', // UCEPROTECT 'psbl.surriel.com' // PSBL ]; // Default options static DEFAULT_OPTIONS = { maxCacheSize: 10000, cacheTTL: 24 * 60 * 60 * 1000, // 24 hours dnsblServers: IPReputationChecker.DEFAULT_DNSBL_SERVERS, highRiskThreshold: ReputationThreshold.HIGH_RISK, mediumRiskThreshold: ReputationThreshold.MEDIUM_RISK, lowRiskThreshold: ReputationThreshold.LOW_RISK, enableLocalCache: true, enableDNSBL: true, enableIPInfo: true }; /** * Constructor for IPReputationChecker * @param options Configuration options * @param storageManager Optional StorageManager instance for persistence */ constructor(options = {}, storageManager) { // Merge with default options this.options = { ...IPReputationChecker.DEFAULT_OPTIONS, ...options }; this.storageManager = storageManager; // If no storage manager provided, log warning if (!storageManager && this.options.enableLocalCache) { logger.log('warn', '⚠️ WARNING: IPReputationChecker initialized without StorageManager.\n' + ' IP reputation cache will only be stored to filesystem.\n' + ' Consider passing a StorageManager instance for better storage flexibility.'); } // Initialize reputation cache this.reputationCache = new LRUCache({ max: this.options.maxCacheSize, ttl: this.options.cacheTTL, // Cache TTL }); // Load cache from disk if enabled if (this.options.enableLocalCache) { // Fire and forget the load operation this.loadCache().catch(error => { logger.log('error', `Failed to load IP reputation cache during initialization: ${error.message}`); }); } } /** * Get the singleton instance of the checker * @param options Configuration options * @param storageManager Optional StorageManager instance for persistence * @returns Singleton instance */ static getInstance(options = {}, storageManager) { if (!IPReputationChecker.instance) { IPReputationChecker.instance = new IPReputationChecker(options, storageManager); } return IPReputationChecker.instance; } /** * Check an IP address's reputation * @param ip IP address to check * @returns Reputation check result */ async checkReputation(ip) { try { // Validate IP address format if (!this.isValidIPAddress(ip)) { logger.log('warn', `Invalid IP address format: ${ip}`); return this.createErrorResult(ip, 'Invalid IP address format'); } // Check cache first const cachedResult = this.reputationCache.get(ip); if (cachedResult) { logger.log('info', `Using cached reputation data for IP ${ip}`, { score: cachedResult.score, isSpam: cachedResult.isSpam }); return cachedResult; } // Try Rust bridge first (parallel DNSBL via tokio — faster than Node sequential DNS) const bridge = RustSecurityBridge.getInstance(); if (bridge.running) { try { const rustResult = await bridge.checkIpReputation(ip); const result = { score: rustResult.score, isSpam: rustResult.listed_count > 0, isProxy: rustResult.ip_type === 'proxy', isTor: rustResult.ip_type === 'tor', isVPN: rustResult.ip_type === 'vpn', blacklists: rustResult.dnsbl_results .filter(d => d.listed) .map(d => d.server), timestamp: Date.now(), }; this.reputationCache.set(ip, result); if (this.options.enableLocalCache) { this.saveCache().catch(error => { logger.log('error', `Failed to save IP reputation cache: ${error.message}`); }); } this.logReputationCheck(ip, result); return result; } catch (err) { logger.log('warn', `Rust IP reputation check failed, falling back to TS: ${err.message}`); } } // Fallback: TypeScript DNSBL implementation const result = { score: 100, // Start with perfect score isSpam: false, isProxy: false, isTor: false, isVPN: false, timestamp: Date.now() }; // Check IP against DNS blacklists if enabled if (this.options.enableDNSBL) { const dnsblResult = await this.checkDNSBL(ip); // Update result with DNSBL information result.score -= dnsblResult.listCount * 10; // Subtract 10 points per blacklist result.isSpam = dnsblResult.listCount > 0; result.blacklists = dnsblResult.lists; } // Get additional IP information if enabled if (this.options.enableIPInfo) { const ipInfo = await this.getIPInfo(ip); // Update result with IP info result.country = ipInfo.country; result.asn = ipInfo.asn; result.org = ipInfo.org; // Adjust score based on IP type if (ipInfo.type === IPType.PROXY || ipInfo.type === IPType.TOR || ipInfo.type === IPType.VPN) { result.score -= 30; // Subtract 30 points for proxies, Tor, VPNs // Set proxy flags result.isProxy = ipInfo.type === IPType.PROXY; result.isTor = ipInfo.type === IPType.TOR; result.isVPN = ipInfo.type === IPType.VPN; } } // Ensure score is between 0 and 100 result.score = Math.max(0, Math.min(100, result.score)); // Update cache with result this.reputationCache.set(ip, result); // Save cache if enabled if (this.options.enableLocalCache) { // Fire and forget the save operation this.saveCache().catch(error => { logger.log('error', `Failed to save IP reputation cache: ${error.message}`); }); } // Log the reputation check this.logReputationCheck(ip, result); return result; } catch (error) { logger.log('error', `Error checking IP reputation for ${ip}: ${error.message}`, { ip, stack: error.stack }); return this.createErrorResult(ip, error.message); } } /** * Check an IP against DNS blacklists * @param ip IP address to check * @returns DNSBL check results */ async checkDNSBL(ip) { try { // Reverse the IP for DNSBL queries const reversedIP = this.reverseIP(ip); const results = await Promise.allSettled(this.options.dnsblServers.map(async (server) => { try { const lookupDomain = `${reversedIP}.${server}`; await plugins.dns.promises.resolve(lookupDomain); return server; // IP is listed in this DNSBL } catch (error) { if (error.code === 'ENOTFOUND') { return null; // IP is not listed in this DNSBL } throw error; // Other error } })); // Extract successful lookups (listed in DNSBL) const lists = results .filter((result) => result.status === 'fulfilled' && result.value !== null) .map(result => result.value); return { listCount: lists.length, lists }; } catch (error) { logger.log('error', `Error checking DNSBL for ${ip}: ${error.message}`); return { listCount: 0, lists: [] }; } } /** * Get information about an IP address * @param ip IP address to check * @returns IP information */ async getIPInfo(ip) { try { // In a real implementation, this would use an IP data service API // For this implementation, we'll use a simplified approach // Check if it's a known Tor exit node (simplified) const isTor = ip.startsWith('171.25.') || ip.startsWith('185.220.') || ip.startsWith('95.216.'); // Check if it's a known VPN (simplified) const isVPN = ip.startsWith('185.156.') || ip.startsWith('37.120.'); // Check if it's a known proxy (simplified) const isProxy = ip.startsWith('34.92.') || ip.startsWith('34.206.'); // Determine IP type let type = IPType.UNKNOWN; if (isTor) { type = IPType.TOR; } else if (isVPN) { type = IPType.VPN; } else if (isProxy) { type = IPType.PROXY; } else { // Simple datacenters detection (major cloud providers) if (ip.startsWith('13.') || // AWS ip.startsWith('35.') || // Google Cloud ip.startsWith('52.') || // AWS ip.startsWith('34.') || // Google Cloud ip.startsWith('104.') // Various providers ) { type = IPType.DATACENTER; } else { type = IPType.RESIDENTIAL; } } // Return the information return { country: this.determineCountry(ip), // Simplified, would use geolocation service asn: 'AS12345', // Simplified, would look up real ASN org: this.determineOrg(ip), // Simplified, would use real org data type }; } catch (error) { logger.log('error', `Error getting IP info for ${ip}: ${error.message}`); return { type: IPType.UNKNOWN }; } } /** * Simplified method to determine country from IP * In a real implementation, this would use a geolocation database or service * @param ip IP address * @returns Country code */ determineCountry(ip) { // Simplified mapping for demo purposes if (ip.startsWith('13.') || ip.startsWith('52.')) return 'US'; if (ip.startsWith('35.') || ip.startsWith('34.')) return 'US'; if (ip.startsWith('185.')) return 'NL'; if (ip.startsWith('171.')) return 'DE'; return 'XX'; // Unknown } /** * Simplified method to determine organization from IP * In a real implementation, this would use an IP-to-org database or service * @param ip IP address * @returns Organization name */ determineOrg(ip) { // Simplified mapping for demo purposes if (ip.startsWith('13.') || ip.startsWith('52.')) return 'Amazon AWS'; if (ip.startsWith('35.') || ip.startsWith('34.')) return 'Google Cloud'; if (ip.startsWith('185.156.')) return 'NordVPN'; if (ip.startsWith('37.120.')) return 'ExpressVPN'; if (ip.startsWith('185.220.')) return 'Tor Exit Node'; return 'Unknown'; } /** * Reverse an IP address for DNSBL lookups (e.g., 1.2.3.4 -> 4.3.2.1) * @param ip IP address to reverse * @returns Reversed IP for DNSBL queries */ reverseIP(ip) { return ip.split('.').reverse().join('.'); } /** * Create an error result for when reputation check fails * @param ip IP address * @param errorMessage Error message * @returns Error result */ createErrorResult(ip, errorMessage) { return { score: 50, // Neutral score for errors isSpam: false, isProxy: false, isTor: false, isVPN: false, timestamp: Date.now(), error: errorMessage }; } /** * Validate IP address format * @param ip IP address to validate * @returns Whether the IP is valid */ isValidIPAddress(ip) { // IPv4 regex pattern const ipv4Pattern = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; return ipv4Pattern.test(ip); } /** * Log reputation check to security logger * @param ip IP address * @param result Reputation result */ logReputationCheck(ip, result) { // Determine log level based on reputation score let logLevel = SecurityLogLevel.INFO; if (result.score < this.options.highRiskThreshold) { logLevel = SecurityLogLevel.WARN; } else if (result.score < this.options.mediumRiskThreshold) { logLevel = SecurityLogLevel.INFO; } // Log the check SecurityLogger.getInstance().logEvent({ level: logLevel, type: SecurityEventType.IP_REPUTATION, message: `IP reputation check ${result.isSpam ? 'flagged spam' : 'completed'} for ${ip}`, ipAddress: ip, details: { score: result.score, isSpam: result.isSpam, isProxy: result.isProxy, isTor: result.isTor, isVPN: result.isVPN, country: result.country, blacklists: result.blacklists }, success: !result.isSpam }); } /** * Save cache to disk or storage manager */ async saveCache() { try { // Convert cache entries to serializable array const entries = Array.from(this.reputationCache.entries()).map(([ip, data]) => ({ ip, data })); // Only save if we have entries if (entries.length === 0) { return; } const cacheData = JSON.stringify(entries); // Save to storage manager if available if (this.storageManager) { await this.storageManager.set('/security/ip-reputation-cache.json', cacheData); logger.log('info', `Saved ${entries.length} IP reputation cache entries to StorageManager`); } else { // Fall back to filesystem const cacheDir = plugins.path.join(paths.dataDir, 'security'); await plugins.smartfs.directory(cacheDir).recursive().create(); const cacheFile = plugins.path.join(cacheDir, 'ip_reputation_cache.json'); await plugins.smartfs.file(cacheFile).write(cacheData); logger.log('info', `Saved ${entries.length} IP reputation cache entries to disk`); } } catch (error) { logger.log('error', `Failed to save IP reputation cache: ${error.message}`); } } /** * Load cache from disk or storage manager */ async loadCache() { try { let cacheData = null; let fromFilesystem = false; // Try to load from storage manager first if (this.storageManager) { try { cacheData = await this.storageManager.get('/security/ip-reputation-cache.json'); if (!cacheData) { // Check if data exists in filesystem and migrate it const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json'); if (plugins.fs.existsSync(cacheFile)) { logger.log('info', 'Migrating IP reputation cache from filesystem to StorageManager'); cacheData = plugins.fs.readFileSync(cacheFile, 'utf8'); fromFilesystem = true; // Migrate to storage manager await this.storageManager.set('/security/ip-reputation-cache.json', cacheData); logger.log('info', 'IP reputation cache migrated to StorageManager successfully'); // Optionally delete the old file after successful migration try { plugins.fs.unlinkSync(cacheFile); logger.log('info', 'Old cache file removed after migration'); } catch (deleteError) { logger.log('warn', `Could not delete old cache file: ${deleteError.message}`); } } } } catch (error) { logger.log('error', `Error loading from StorageManager: ${error.message}`); } } else { // No storage manager, load from filesystem const cacheFile = plugins.path.join(paths.dataDir, 'security', 'ip_reputation_cache.json'); if (plugins.fs.existsSync(cacheFile)) { cacheData = plugins.fs.readFileSync(cacheFile, 'utf8'); fromFilesystem = true; } } // Parse and restore cache if data was found if (cacheData) { const entries = JSON.parse(cacheData); // Validate and filter entries const now = Date.now(); const validEntries = entries.filter(entry => { const age = now - entry.data.timestamp; return age < this.options.cacheTTL; // Only load entries that haven't expired }); // Restore cache for (const entry of validEntries) { this.reputationCache.set(entry.ip, entry.data); } const source = fromFilesystem ? 'disk' : 'StorageManager'; logger.log('info', `Loaded ${validEntries.length} IP reputation cache entries from ${source}`); } } catch (error) { logger.log('error', `Failed to load IP reputation cache: ${error.message}`); } } /** * Get the risk level for a reputation score * @param score Reputation score (0-100) * @returns Risk level description */ static getRiskLevel(score) { if (score < ReputationThreshold.HIGH_RISK) { return 'high'; } else if (score < ReputationThreshold.MEDIUM_RISK) { return 'medium'; } else if (score < ReputationThreshold.LOW_RISK) { return 'low'; } else { return 'trusted'; } } /** * Update the storage manager after instantiation * This is useful when the storage manager is not available at construction time * @param storageManager The StorageManager instance to use */ updateStorageManager(storageManager) { this.storageManager = storageManager; logger.log('info', 'IPReputationChecker storage manager updated'); // If cache is enabled and we have entries, save them to the new storage manager if (this.options.enableLocalCache && this.reputationCache.size > 0) { this.saveCache().catch(error => { logger.log('error', `Failed to save cache to new storage manager: ${error.message}`); }); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5pcHJlcHV0YXRpb25jaGVja2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvc2VjdXJpdHkvY2xhc3Nlcy5pcHJlcHV0YXRpb25jaGVja2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxLQUFLLE1BQU0sYUFBYSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDdEMsT0FBTyxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQ2xHLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxXQUFXLENBQUM7QUFtQnJDOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksbUJBSVg7QUFKRCxXQUFZLG1CQUFtQjtJQUM3Qix3RUFBYyxDQUFBO0lBQ2QsNEVBQWdCLENBQUE7SUFDaEIsc0VBQWEsQ0FBQSxDQUFRLDREQUE0RDtBQUNuRixDQUFDLEVBSlcsbUJBQW1CLEtBQW5CLG1CQUFtQixRQUk5QjtBQUVEOztHQUVHO0FBQ0gsTUFBTSxDQUFOLElBQVksTUFPWDtBQVBELFdBQVksTUFBTTtJQUNoQixxQ0FBMkIsQ0FBQTtJQUMzQixtQ0FBeUIsQ0FBQTtJQUN6Qix5QkFBZSxDQUFBO0lBQ2YscUJBQVcsQ0FBQTtJQUNYLHFCQUFXLENBQUE7SUFDWCw2QkFBbUIsQ0FBQTtBQUNyQixDQUFDLEVBUFcsTUFBTSxLQUFOLE1BQU0sUUFPakI7QUFpQkQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ3RCLE1BQU0sQ0FBQyxRQUFRLENBQXNCO0lBQ3JDLGVBQWUsQ0FBc0M7SUFDckQsT0FBTyxDQUFpQztJQUN4QyxjQUFjLENBQU8sQ0FBQywwQkFBMEI7SUFFeEQs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