UNPKG

trojanhorse-js

Version:

A comprehensive JavaScript library for fetching, managing, and analyzing global threat intelligence from multiple open-source feeds and security news sources. Unlike its mythological namesake, this Trojan protects your digital fortress.

1,228 lines (1,178 loc) 171 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('crypto-js'), require('axios')) : typeof define === 'function' && define.amd ? define(['exports', 'crypto-js', 'axios'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.TrojanHorse = {}, global.CryptoJS, global.axios)); })(this, (function (exports, CryptoJS, axios) { 'use strict'; var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; class TrojanHorseError extends Error { code; statusCode; details; constructor(message, code, statusCode, details) { super(message); this.name = 'TrojanHorseError'; this.code = code; this.statusCode = statusCode || 500; this.details = details || {}; Error.captureStackTrace(this, TrojanHorseError); } } class SecurityError extends TrojanHorseError { constructor(message, details) { super(message, 'SECURITY_ERROR', 403, details); this.name = 'SecurityError'; } } class AuthenticationError extends TrojanHorseError { constructor(message, details) { super(message, 'AUTH_ERROR', 401, details); this.name = 'AuthenticationError'; } } class RateLimitError extends TrojanHorseError { retryAfter; constructor(message, retryAfter, details) { super(message, 'RATE_LIMIT_ERROR', 429, details); this.name = 'RateLimitError'; this.retryAfter = retryAfter || 60; } } let argon2 = null; let argon2Available = false; async function loadArgon2() { if (argon2Available) { return argon2; } try { if (typeof process !== 'undefined' && process.versions?.node) { try { if (typeof require !== 'undefined') { argon2 = require('argon2'); argon2Available = true; return argon2; } const { createRequire } = await import('module'); const moduleRequire = createRequire((typeof document === 'undefined' && typeof location === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : typeof document === 'undefined' ? location.href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('trojanhorse.umd.js', document.baseURI).href)) || new URL('file://' + __filename).href); argon2 = moduleRequire('argon2'); argon2Available = true; return argon2; } catch (importError) { try { const argon2Module = await import('argon2'); argon2 = argon2Module.default || argon2Module; argon2Available = true; return argon2; } catch (dynamicImportError) { console.warn('Argon2 not available, falling back to PBKDF2'); argon2Available = false; return null; } } } else { console.warn('Argon2 not available in browser environment, falling back to PBKDF2'); return null; } } catch (error) { console.warn('Argon2 unavailable, falling back to PBKDF2:', String(error)); return null; } } class CryptoEngine { static ALGORITHM = 'AES-256-GCM'; static KEY_SIZE = 32; static IV_SIZE_GCM = 12; static SALT_SIZE = 32; static ARGON2_MEMORY = 64 * 1024; static ARGON2_TIME = 3; static ARGON2_PARALLELISM = 4; static PBKDF2_ITERATIONS = 100000; argon2Instance = null; constructor() { if (typeof crypto === 'undefined' && typeof window?.crypto === 'undefined') { throw new SecurityError('No cryptographic API available'); } this.initializeArgon2(); } async initializeArgon2() { try { this.argon2Instance = await loadArgon2(); } catch (error) { console.warn('Failed to initialize Argon2, using PBKDF2 fallback'); } } generateSecureRandom(length) { if (length <= 0 || length > 1024) { throw new SecurityError('Invalid random length: must be 1-1024 bytes'); } try { if (typeof crypto !== 'undefined' && crypto.getRandomValues) { const array = new Uint8Array(length); crypto.getRandomValues(array); return array; } else if (typeof window !== 'undefined' && window.crypto?.getRandomValues) { const array = new Uint8Array(length); window.crypto.getRandomValues(array); return array; } else { const wordArray = CryptoJS.lib.WordArray.random(length); return new Uint8Array(this.wordArrayToUint8Array(wordArray)); } } catch (error) { throw new SecurityError('Failed to generate secure random bytes', { originalError: error }); } } generateSalt(length = CryptoEngine.SALT_SIZE) { const saltBytes = this.generateSecureRandom(length); return Buffer.from(saltBytes).toString('base64'); } async deriveKey(password, salt, options = {}) { if (!password || password.length < 8) { throw new SecurityError('Password must be at least 8 characters long'); } if (!salt || salt.length < 16) { throw new SecurityError('Salt must be at least 16 characters long'); } if (!this.argon2Instance) { await this.initializeArgon2(); } const saltBuffer = Buffer.from(salt, 'base64'); if (this.argon2Instance) { try { const hash = await this.argon2Instance.hash(password, { type: this.argon2Instance.argon2id, memoryCost: options.memoryCost || CryptoEngine.ARGON2_MEMORY, timeCost: options.timeCost || CryptoEngine.ARGON2_TIME, parallelism: options.parallelism || CryptoEngine.ARGON2_PARALLELISM, hashLength: CryptoEngine.KEY_SIZE, salt: saltBuffer, raw: true }); return { key: hash, method: 'Argon2id' }; } catch (error) { console.warn('Argon2 failed, falling back to PBKDF2:', String(error)); } } try { const key = CryptoJS.PBKDF2(password, salt, { keySize: CryptoEngine.KEY_SIZE / 4, iterations: CryptoEngine.PBKDF2_ITERATIONS, hasher: CryptoJS.algo.SHA256 }); const keyBytes = []; for (let i = 0; i < key.words.length; i++) { const word = key.words[i]; if (word !== undefined) { keyBytes.push((word >> 24) & 0xff); keyBytes.push((word >> 16) & 0xff); keyBytes.push((word >> 8) & 0xff); keyBytes.push(word & 0xff); } } return { key: Buffer.from(keyBytes.slice(0, CryptoEngine.KEY_SIZE)), method: 'PBKDF2-Fallback' }; } catch (error) { throw new SecurityError('Key derivation failed', { originalError: error }); } } async encrypt(data, password, options = {}) { try { if (!data) { throw new SecurityError('Data cannot be empty'); } if (!password || password.length < 8) { throw new SecurityError('Password must be at least 8 characters long'); } const salt = this.generateSalt(CryptoEngine.SALT_SIZE); const iv = this.generateSecureRandom(CryptoEngine.IV_SIZE_GCM); const ivBase64 = Buffer.from(iv).toString('base64'); const keyResult = await this.deriveKey(password, salt, { memoryCost: options.iterations || CryptoEngine.ARGON2_MEMORY, timeCost: CryptoEngine.ARGON2_TIME, parallelism: CryptoEngine.ARGON2_PARALLELISM }); const serializedData = JSON.stringify(data); if (typeof require !== 'undefined') { try { const nodeCrypto = require('crypto'); let cipher; let authTag; try { cipher = nodeCrypto.createCipher('aes-256-gcm', keyResult.key); cipher.setAutoPadding(false); let encrypted = cipher.update(serializedData, 'utf8', 'base64'); encrypted += cipher.final('base64'); authTag = cipher.getAuthTag ? cipher.getAuthTag().toString('base64') : ''; this.secureErase(keyResult.key); password = ''; return { encrypted, authTag, salt, iv: ivBase64, algorithm: CryptoEngine.ALGORITHM, iterations: options.iterations || CryptoEngine.ARGON2_MEMORY, timestamp: Date.now(), memoryKb: CryptoEngine.ARGON2_MEMORY, parallelism: CryptoEngine.ARGON2_PARALLELISM, keyDerivation: keyResult.method }; } catch (gcmError) { throw new Error(`GCM not supported: ${gcmError instanceof Error ? gcmError.message : String(gcmError)}`); } } catch (nodeError) { console.warn('Node.js crypto failed, using CryptoJS fallback:', String(nodeError)); } } const keyWordArray = CryptoJS.enc.Hex.parse(keyResult.key.toString('hex')); const ivWordArray = CryptoJS.enc.Base64.parse(ivBase64); const encrypted = CryptoJS.AES.encrypt(serializedData, keyWordArray, { iv: ivWordArray, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); const encryptedBase64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64); const authTag = CryptoJS.HmacSHA256(encryptedBase64 + ivBase64 + salt, keyWordArray).toString(); this.secureErase(keyResult.key); password = ''; return { encrypted: encryptedBase64, authTag, salt, iv: ivBase64, algorithm: CryptoEngine.ALGORITHM, iterations: options.iterations || CryptoEngine.ARGON2_MEMORY, timestamp: Date.now(), memoryKb: CryptoEngine.ARGON2_MEMORY, parallelism: CryptoEngine.ARGON2_PARALLELISM, keyDerivation: keyResult.method }; } catch (error) { if (error instanceof SecurityError) { throw error; } throw new SecurityError('Encryption failed', { originalError: error }); } } async decrypt(vault, password) { try { if (!vault?.encrypted || !vault.salt || !vault.iv || !vault.authTag) { throw new SecurityError('Invalid vault structure'); } if (!password || password.length < 8) { throw new SecurityError('Invalid password'); } const keyResult = await this.deriveKey(password, vault.salt, { memoryCost: vault.memoryKb || CryptoEngine.ARGON2_MEMORY, timeCost: CryptoEngine.ARGON2_TIME, parallelism: vault.parallelism || CryptoEngine.ARGON2_PARALLELISM }); if (typeof require !== 'undefined') { try { const nodeCrypto = require('crypto'); try { const decipher = nodeCrypto.createDecipher('aes-256-gcm', keyResult.key); if (decipher.setAuthTag && vault.authTag) { decipher.setAuthTag(Buffer.from(vault.authTag, 'base64')); } let decryptedString = decipher.update(vault.encrypted, 'base64', 'utf8'); decryptedString += decipher.final('utf8'); this.secureErase(keyResult.key); password = ''; return JSON.parse(decryptedString); } catch (gcmError) { throw new Error(`GCM decrypt not supported: ${gcmError instanceof Error ? gcmError.message : String(gcmError)}`); } } catch (nodeError) { console.warn('Node.js crypto GCM failed, using CryptoJS fallback:', String(nodeError)); } } const keyWordArray = CryptoJS.enc.Hex.parse(keyResult.key.toString('hex')); const ivWordArray = CryptoJS.enc.Base64.parse(vault.iv); const expectedAuthTag = CryptoJS.HmacSHA256(vault.encrypted + vault.iv + vault.salt, keyWordArray).toString(); if (expectedAuthTag !== vault.authTag) { throw new SecurityError('Authentication verification failed - data may be corrupted or tampered'); } try { const decrypted = CryptoJS.AES.decrypt(vault.encrypted, keyWordArray, { iv: ivWordArray, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); const plaintextString = decrypted.toString(CryptoJS.enc.Utf8); if (!plaintextString) { throw new SecurityError('Decryption failed - malformed data or invalid key'); } return JSON.parse(plaintextString); } catch (decryptError) { const errorMessage = decryptError instanceof Error ? decryptError.message : String(decryptError); console.error('🔍 AES Decryption Error:', errorMessage); throw new SecurityError(`Decryption failed - ${errorMessage}`); } } catch (error) { if (error instanceof SecurityError) { throw error; } throw new SecurityError('Decryption failed', { originalError: error }); } } hash(data) { if (!data) { throw new SecurityError('Data to hash cannot be empty'); } try { return CryptoJS.SHA256(data).toString(CryptoJS.enc.Hex); } catch (error) { throw new SecurityError('Hashing failed', { originalError: error }); } } hmac(data, key) { if (!data || !key) { throw new SecurityError('Data and key cannot be empty'); } try { return CryptoJS.HmacSHA256(data, key).toString(CryptoJS.enc.Hex); } catch (error) { throw new SecurityError('HMAC generation failed', { originalError: error }); } } secureErase(data) { try { if (Buffer.isBuffer(data)) { const randomData = this.generateSecureRandom(data.length); for (let i = 0; i < data.length; i++) { const value = randomData[i]; if (value !== undefined) { data[i] = value; } } data.fill(0); } else if (data && typeof data === 'object' && data.words) { for (let i = 0; i < data.words.length; i++) { data.words[i] = 0; } } else if (typeof data === 'string') { data = ''; } else if (data instanceof Uint8Array) { const randomData = this.generateSecureRandom(data.length); for (let i = 0; i < data.length; i++) { const value = randomData[i]; if (value !== undefined) { data[i] = value; } } data.fill(0); } } catch (error) { console.warn('Secure erase failed:', error); } } wordArrayToUint8Array(wordArray) { const words = wordArray.words; const sigBytes = wordArray.sigBytes; const bytes = []; for (let i = 0; i < sigBytes; i++) { bytes.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff); } return bytes; } validateEncryptionParams(vault) { try { return !!(vault && typeof vault.encrypted === 'string' && typeof vault.authTag === 'string' && typeof vault.salt === 'string' && typeof vault.iv === 'string' && vault.algorithm === CryptoEngine.ALGORITHM && typeof vault.timestamp === 'number' && vault.timestamp > 0); } catch (error) { return false; } } getCryptoInfo() { const hasNodeCrypto = typeof require !== 'undefined'; return { implementation: hasNodeCrypto ? 'Node.js Crypto' : 'CryptoJS Fallback', algorithm: CryptoEngine.ALGORITHM, keyDerivation: 'Argon2id', secure: true }; } isSecureContext() { if (typeof window === 'undefined') { return true; } return window.isSecureContext || location.protocol === 'https:'; } } class KeyVault { cryptoEngine; encryptedVault = null; decryptedKeys = null; isLocked = true; options; autoLockTimer = null; lastAccessTime = null; failedAttempts = 0; maxFailedAttempts = 5; lockoutDuration = 300000; constructor(options = {}) { this.cryptoEngine = new CryptoEngine(); this.options = { algorithm: 'AES-256-GCM', keyDerivation: 'Argon2id', iterations: 65536, saltBytes: 32, autoLock: true, lockTimeout: 300000, requireMFA: false, ...options }; } extractApiKey(keyData) { if (!keyData) { return undefined; } if (typeof keyData === 'string') { return keyData; } if (typeof keyData === 'object') { return keyData.key || keyData.secret || keyData.token; } return undefined; } normalizeApiKeys(apiKeys) { const normalized = {}; for (const [provider, keyData] of Object.entries(apiKeys)) { const stringKey = this.extractApiKey(keyData); if (stringKey) { normalized[provider] = stringKey; } } return normalized; } async createVault(password, apiKeys) { this.validatePassword(password); this.validateApiKeys(apiKeys); try { const vault = await this.cryptoEngine.encrypt(apiKeys, password, this.options); this.encryptedVault = vault; this.decryptedKeys = this.normalizeApiKeys(apiKeys); this.isLocked = false; this.updateAccessTime(); this.setupAutoLock(); return vault; } catch (error) { throw new SecurityError('Failed to create vault', { originalError: error }); } } loadVault(vault) { if (!this.cryptoEngine.validateEncryptionParams(vault)) { throw new SecurityError('Invalid vault structure'); } this.encryptedVault = vault; this.isLocked = true; this.decryptedKeys = null; } async unlock(password) { if (!this.encryptedVault) { throw new SecurityError('No vault loaded'); } if (this.failedAttempts >= this.maxFailedAttempts) { throw new AuthenticationError('Vault is locked due to too many failed attempts'); } this.validatePassword(password); try { const decryptedData = await this.cryptoEngine.decrypt(this.encryptedVault, password); this.validateApiKeys(decryptedData); this.decryptedKeys = this.normalizeApiKeys(decryptedData); this.isLocked = false; this.failedAttempts = 0; this.updateAccessTime(); this.setupAutoLock(); } catch (error) { this.failedAttempts++; if (this.failedAttempts >= this.maxFailedAttempts) { this.lockVault(); setTimeout(() => { this.failedAttempts = 0; }, this.lockoutDuration); } throw new AuthenticationError('Failed to unlock vault - invalid password'); } } lock() { this.lockVault(); } getApiKey(provider) { if (this.isLocked || !this.decryptedKeys) { throw new SecurityError('Vault is locked - please unlock first'); } this.updateAccessTime(); const key = this.decryptedKeys[provider]; if (!key) { throw new SecurityError(`API key for provider '${provider}' not found`); } return key; } async setApiKey(provider, apiKey, password) { if (this.isLocked || !this.decryptedKeys) { throw new SecurityError('Vault is locked - please unlock first'); } if (!provider || !apiKey) { throw new SecurityError('Provider and API key cannot be empty'); } this.decryptedKeys[provider] = apiKey; const newVault = await this.cryptoEngine.encrypt(this.decryptedKeys, password, this.options); this.encryptedVault = newVault; this.updateAccessTime(); } async removeApiKey(provider, password) { if (this.isLocked || !this.decryptedKeys) { throw new SecurityError('Vault is locked - please unlock first'); } if (!this.decryptedKeys[provider]) { throw new SecurityError(`API key for provider '${provider}' not found`); } delete this.decryptedKeys[provider]; const newVault = await this.cryptoEngine.encrypt(this.decryptedKeys, password, this.options); this.encryptedVault = newVault; this.updateAccessTime(); } async rotateKey(provider, newKey, options = {}) { if (this.isLocked || !this.decryptedKeys) { throw new SecurityError('Vault is locked - please unlock first'); } if (!this.decryptedKeys[provider]) { throw new SecurityError(`API key for provider '${provider}' not found`); } const { gracePeriod = 0, password, notifyRotation = true } = options; const oldKey = this.decryptedKeys[provider]; try { this.decryptedKeys[provider] = newKey; this.updateAccessTime(); if (password && this.encryptedVault) { const updatedVault = await this.cryptoEngine.encrypt(this.decryptedKeys, password, this.options); this.encryptedVault = updatedVault; } if (gracePeriod > 0) { setTimeout(() => { if (typeof oldKey === 'string') { const keyArray = oldKey.split(''); for (let i = 0; i < keyArray.length; i++) { keyArray[i] = Math.random().toString(36).charAt(0); } } }, gracePeriod); } if (notifyRotation) { console.info(`🔄 API key rotated for provider: ${provider}`); } this.auditLog('info', `API key rotated for provider: ${provider}`); } catch (error) { if (this.decryptedKeys && oldKey) { this.decryptedKeys[provider] = oldKey; } throw new SecurityError(`Key rotation failed for ${provider}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async rotateMultipleKeys(keyUpdates, options = {}) { const results = { success: [], failed: {} }; const { continueOnError = true } = options; for (const [provider, newKey] of Object.entries(keyUpdates)) { try { await this.rotateKey(provider, newKey, { ...options, notifyRotation: false }); results.success.push(provider); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; results.failed[provider] = errorMessage; console.error(`❌ Failed to rotate key for ${provider}: ${errorMessage}`); if (!continueOnError) { throw error; } } } console.info(`🔄 Batch key rotation completed: ${results.success.length} successful, ${Object.keys(results.failed).length} failed`); return results; } setupKeyRotation(config) { const { providers, rotationInterval, keyGenerator, password } = config; return setInterval(async () => { console.info('🔄 Starting scheduled key rotation...'); const keyUpdates = {}; for (const provider of providers) { try { keyUpdates[provider] = await keyGenerator(provider); } catch (error) { console.error(`Failed to generate new key for ${provider}:`, error); } } try { const results = await this.rotateMultipleKeys(keyUpdates, { password, gracePeriod: 5 * 60 * 1000, continueOnError: true }); console.info(`✅ Scheduled rotation completed: ${results.success.length} keys rotated`); } catch (error) { console.error('❌ Scheduled key rotation failed:', error); } }, rotationInterval); } getStatus() { return { isLocked: this.isLocked, hasVault: !!this.encryptedVault, keyCount: this.decryptedKeys ? Object.keys(this.decryptedKeys).length : 0, lastAccess: this.lastAccessTime, autoLockEnabled: this.options.autoLock || false, failedAttempts: this.failedAttempts }; } getProviders() { if (this.isLocked || !this.decryptedKeys) { throw new SecurityError('Vault is locked - please unlock first'); } this.updateAccessTime(); return Object.keys(this.decryptedKeys); } async testApiKey(provider) { const key = this.getApiKey(provider); if (!key || key.length < 8) { return false; } return true; } exportVault() { if (!this.encryptedVault) { throw new SecurityError('No vault to export'); } return { ...this.encryptedVault }; } lockVault() { this.isLocked = true; this.decryptedKeys = null; this.clearAutoLockTimer(); if (this.decryptedKeys) { this.cryptoEngine.secureErase(this.decryptedKeys); } } updateAccessTime() { this.lastAccessTime = new Date(); if (this.options.autoLock) { this.setupAutoLock(); } } setupAutoLock() { this.clearAutoLockTimer(); if (this.options.autoLock && this.options.lockTimeout) { this.autoLockTimer = setTimeout(() => { this.lockVault(); }, this.options.lockTimeout); } } clearAutoLockTimer() { if (this.autoLockTimer) { clearTimeout(this.autoLockTimer); this.autoLockTimer = null; } } validatePassword(password) { if (!password || typeof password !== 'string') { throw new SecurityError('Password is required'); } if (password.length < 12) { throw new SecurityError('Password must be at least 12 characters long'); } const entropy = this.calculatePasswordEntropy(password); if (entropy < 50) { throw new SecurityError(`Password is too weak (entropy: ${entropy.toFixed(1)} bits). Minimum required: 50 bits`); } if (this.hasWeakPatterns(password)) { throw new SecurityError('Password contains weak patterns'); } } calculatePasswordEntropy(password) { const charSets = { lowercase: /[a-z]/.test(password) ? 26 : 0, uppercase: /[A-Z]/.test(password) ? 26 : 0, digits: /[0-9]/.test(password) ? 10 : 0, symbols: /[^a-zA-Z0-9]/.test(password) ? 32 : 0 }; const charsetSize = Object.values(charSets).reduce((sum, size) => sum + size, 0); if (charsetSize === 0) { return 0; } const entropy = password.length * Math.log2(charsetSize); const uniqueChars = new Set(password).size; const repetitionPenalty = uniqueChars / password.length; return entropy * repetitionPenalty; } hasWeakPatterns(password) { const weakPatterns = [ /(.)\1{2,}/, /012|123|234|345|456|567|678|789|890/, /abc|bcd|cde|def|efg|fgh|ghi|hij|ijk|jkl|klm|lmn|mno|nop|opq|pqr|qrs|rst|stu|tuv|uvw|vwx|wxy|xyz/i, /^(password|admin|user|login|qwerty|123456|letmein)$/i, /^.{1,3}$/ ]; return weakPatterns.some(pattern => pattern.test(password)); } validateApiKeys(apiKeys) { if (!apiKeys || typeof apiKeys !== 'object') { throw new SecurityError('API keys must be an object'); } const keys = Object.keys(apiKeys); if (keys.length === 0) { throw new SecurityError('At least one API key is required'); } keys.forEach(provider => { const key = apiKeys[provider]; if (!key || typeof key !== 'string' || key.length < 8) { throw new SecurityError(`Invalid API key for provider: ${provider}`); } }); } sanitizeProvider(provider) { return provider.replace(/[^a-zA-Z0-9_-]/g, ''); } auditLog(level, message, details) { const timestamp = new Date().toISOString(); console[level](`[KeyVault Audit] ${timestamp} - ${message}`, details || ''); } } class ThreatCorrelationEngine { config; constructor(config = {}) { if (config.minimumSources !== undefined && config.minimumSources < 1) { throw new Error('Invalid configuration: minimumSources must be at least 1'); } if (config.consensusThreshold !== undefined && (config.consensusThreshold < 0 || config.consensusThreshold > 1)) { throw new Error('Invalid configuration: consensusThreshold must be between 0 and 1'); } this.config = { minimumSources: 2, consensusThreshold: 0.5, temporalWindowDays: 7, confidenceWeighting: {}, enableGeolocationAnalysis: false, enablePatternDetection: true, riskScoreWeights: { consensus: 0.4, recency: 0.3, severity: 0.3, sourceReliability: 0.2 }, ...config }; } async correlate(indicators) { if (indicators.length === 0) { return { relatedIndicators: [], crossReferences: [], enrichmentData: { reputation: { overall: 0, categories: [] } }, riskFactors: [], patterns: [], correlationScore: 0, consensusLevel: 'weak', riskScore: 0, sources: [], indicators: [] }; } const sources = [...new Set(indicators.map(i => i.source))]; const correlationScore = Math.min(indicators.length / this.config.minimumSources, 1); const consensusLevel = correlationScore > 0.7 ? 'strong' : correlationScore > 0.4 ? 'moderate' : 'weak'; const riskScore = indicators.reduce((sum, i) => sum + i.confidence, 0) / indicators.length; return { relatedIndicators: indicators, crossReferences: [], enrichmentData: { reputation: { overall: riskScore * 100, categories: [] } }, riskFactors: [], patterns: [], correlationScore, consensusLevel, riskScore, sources, indicators }; } exportResult(result, format = 'json') { switch (format) { case 'json': return JSON.stringify({ ...result, timestamp: new Date().toISOString() }, null, 2); case 'stix': { const stixObject = { type: 'bundle', id: `bundle--${Date.now()}`, spec_version: '2.1', objects: [{ type: 'indicator', id: `indicator--${Date.now()}`, created: new Date().toISOString(), modified: new Date().toISOString(), pattern: result.relatedIndicators?.[0]?.value || 'unknown', labels: ['malicious-activity'], confidence: Math.round((result.correlationScore || 0) * 100) }] }; return JSON.stringify(stixObject, null, 2); } case 'csv': { const headers = 'type,value,confidence,sources,risk_score\n'; const rows = (result.relatedIndicators || []).map((indicator) => `${indicator.type},${indicator.value},${indicator.confidence},"${(result.sources || []).join(';')}",${result.riskScore || 0}`).join('\n'); return headers + rows; } default: throw new Error(`Unsupported export format: ${format}`); } } addIntegration(_name, _integration) { } async shareResult(_result, _platform) { } } var domain; // This constructor is used to store event handlers. Instantiating this is // faster than explicitly calling `Object.create(null)` to get a "clean" empty // object (tested with v8 v4.9). function EventHandlers() {} EventHandlers.prototype = Object.create(null); function EventEmitter() { EventEmitter.init.call(this); } // nodejs oddity // require('events') === require('events').EventEmitter EventEmitter.EventEmitter = EventEmitter; EventEmitter.usingDomains = false; EventEmitter.prototype.domain = undefined; EventEmitter.prototype._events = undefined; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. EventEmitter.defaultMaxListeners = 10; EventEmitter.init = function() { this.domain = null; if (EventEmitter.usingDomains) { // if there is an active domain, then attach to it. if (domain.active && !(this instanceof domain.Domain)) { this.domain = domain.active; } } if (!this._events || this._events === Object.getPrototypeOf(this)._events) { this._events = new EventHandlers(); this._eventsCount = 0; } this._maxListeners = this._maxListeners || undefined; }; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { if (typeof n !== 'number' || n < 0 || isNaN(n)) throw new TypeError('"n" argument must be a positive number'); this._maxListeners = n; return this; }; function $getMaxListeners(that) { if (that._maxListeners === undefined) return EventEmitter.defaultMaxListeners; return that._maxListeners; } EventEmitter.prototype.getMaxListeners = function getMaxListeners() { return $getMaxListeners(this); }; // These standalone emit* functions are used to optimize calling of event // handlers for fast cases because emit() itself often has a variable number of // arguments and can be deoptimized because of that. These functions always have // the same number of arguments and thus do not get deoptimized, so the code // inside them can execute faster. function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } function emitOne(handler, isFn, self, arg1) { if (isFn) handler.call(self, arg1); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1); } } function emitTwo(handler, isFn, self, arg1, arg2) { if (isFn) handler.call(self, arg1, arg2); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2); } } function emitThree(handler, isFn, self, arg1, arg2, arg3) { if (isFn) handler.call(self, arg1, arg2, arg3); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self, arg1, arg2, arg3); } } function emitMany(handler, isFn, self, args) { if (isFn) handler.apply(self, args); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].apply(self, args); } } EventEmitter.prototype.emit = function emit(type) { var er, handler, len, args, i, events, domain; var doError = (type === 'error'); events = this._events; if (events) doError = (doError && events.error == null); else if (!doError) return false; domain = this.domain; // If there is no 'error' event listener then throw. if (doError) { er = arguments[1]; if (domain) { if (!er) er = new Error('Uncaught, unspecified "error" event'); er.domainEmitter = this; er.domain = domain; er.domainThrown = false; domain.emit('error', er); } else if (er instanceof Error) { throw er; // Unhandled 'error' event } else { // At least give some kind of context to the user var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); err.context = er; throw err; } return false; } handler = events[type]; if (!handler) return false; var isFn = typeof handler === 'function'; len = arguments.length; switch (len) { // fast cases case 1: emitNone(handler, isFn, this); break; case 2: emitOne(handler, isFn, this, arguments[1]); break; case 3: emitTwo(handler, isFn, this, arguments[1], arguments[2]); break; case 4: emitThree(handler, isFn, this, arguments[1], arguments[2], arguments[3]); break; // slower default: args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; emitMany(handler, isFn, this, args); } return true; }; function _addListener(target, type, listener, prepend) { var m; var events; var existing; if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); events = target._events; if (!events) { events = target._events = new EventHandlers(); target._eventsCount = 0; } else { // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (events.newListener) { target.emit('newListener', type, listener.listener ? listener.listener : listener); // Re-assign `events` because a newListener handler could have caused the // this._events to be assigned to a new object events = target._events; } existing = events[type]; } if (!existing) { // Optimize the case of one listener. Don't need the extra array object. existing = events[type] = listener; ++target._eventsCount; } else { if (typeof existing === 'function') { // Adding the second element, need to change to array. existing = events[type] = prepend ? [listener, existing] : [existing, listener]; } else { // If we've already got an array, just append. if (prepend) { existing.unshift(listener); } else { existing.push(listener); } } // Check for listener leak if (!existing.warned) { m = $getMaxListeners(target); if (m && m > 0 && existing.length > m) { existing.warned = true; var w = new Error('Possible EventEmitter memory leak detected. ' + existing.length + ' ' + type + ' listeners added. ' + 'Use emitter.setMaxListeners() to increase limit'); w.name = 'MaxListenersExceededWarning'; w.emitter = target; w.type = type; w.count = existing.length; emitWarning(w); } } } return target; } function emitWarning(e) { typeof console.warn === 'function' ? console.warn(e) : console.log(e); } EventEmitter.prototype.addListener = function addListener(type, listener) { return _addListener(this, type, listener, false); }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.prependListener = function prependListener(type, listener) { return _addListener(this, type, listener, true); }; function _onceWrap(target, type, listener) { var fired = false; function g() { target.removeListener(type, g); if (!fired) { fired = true; listener.apply(target, arguments); } } g.listener = listener; return g; } EventEmitter.prototype.once = function once(type, listener) { if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); this.on(type, _onceWrap(this, type, listener)); return this; }; EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) { if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); this.prependListener(type, _onceWrap(this, type, listener)); return this; }; // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener; if (typeof listener !== 'function') throw new TypeError('"listener" argument must be a function'); events = this._events; if (!events) return this; list = events[type]; if (!list) return this; if (list === listener || (list.listener && list.listener === listener)) { if (--this._eventsCount === 0) this._events = new EventHandlers(); else { delete events[type]; if (events.removeListener) this.emit('removeListener', type, list.listener || listener); } } else if (typeof list !== 'function') { position = -1; for (i = list.length; i-- > 0;) { if (list[i] === listener || (list[i].listener && list[i].listener === listener)) { originalListener = list[i].listener; position = i; break; } } if (position < 0) return this; if (list.length === 1) { list[0] = undefined; if (--this._eventsCount === 0) { this._events = new EventHandlers(); return this; } else { delete events[type]; } } else { spliceOne(list, position); } if (events.removeListener) this.emit('removeListener', type, originalListener || listener); } return this; }; // Alias for removeListener added in NodeJS 10.0 // https://nodejs.org/api/events.html#events_emitter_off_eventname_listener EventEmitter.prototype.off = function(type, listene