UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

813 lines (809 loc) 31.3 kB
'use strict'; var types = require('./types.js'); var ConsoleEncryption = require('./encryption/ConsoleEncryption.js'); require('../../../../../../index.js'); require('../../../../../../types.js'); var hashCore = require('../../../../../../core/hash/hash-core.js'); require('../../../../../../core/hash/hash-types.js'); require('crypto'); require('../../../../../../core/hash/hash-security.js'); require('../../../../../../core/hash/hash-advanced.js'); require('../../../../../../algorithms/hash-algorithms.js'); require('../../../../../../core/keys/keys-types.js'); require('../../../../../../core/keys/keys-logger.js'); require('../../../../../../core/keys/keys-utils.js'); require('../../../../../../core/keys/algorithms/mods/PBKDF2Algo.js'); var randomCore = require('../../../../../../core/random/random-core.js'); require('../../../../../../core/random/random-types.js'); require('../../../../../../core/random/random-sources.js'); require('nehonix-uri-processor'); require('../../../../../../utils/memory/index.js'); require('argon2'); require('child_process'); require('../../../../../../types/secure-memory.js'); require('https'); require('../../../../../../components/runtime-verification.js'); require('../../../../../../components/tamper-evident-logging.js'); require('../../../../../../components/secure-string/advanced/entropy-analyzer.js'); require('../../../../../../components/secure-string/advanced/quantum-safe.js'); require('../../../../../../components/secure-string/advanced/performance-monitor.js'); require('nehoid'); require('../../../../../../core/password/index.js'); var index = require('../../../../../../components/fortified-function/index.js'); /*************************************************************************** * FortifyJS - Secure Array Types * * This file contains type definitions for the SecureArray architecture * * @author Nehonix * @license MIT * * Copyright (c) 2025 Nehonix. All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. ***************************************************************************** */ class ConsoleInterceptor { constructor(logger, config) { this.originalConsole = {}; this.isIntercepting = false; this.errorCount = 0; this.lastSecondInterceptions = 0; this.lastSecondTimestamp = 0; this.performanceBuffer = []; // Rate limiting this.rateLimitCounter = 0; this.rateLimitWindow = Date.now(); // Recursion prevention this.isProcessing = false; this.processingDepth = 0; this.maxProcessingDepth = 3; // Encryption handler this.encryptionHandler = null; this.logger = logger; // Merge with user configuration const userConfig = config?.consoleInterception || {}; this.config = { ...types.DEFAULT_CONSOLE_CONFIG, ...userConfig, filters: { ...types.DEFAULT_CONSOLE_CONFIG.filters, ...userConfig.filters, }, fallback: { ...types.DEFAULT_CONSOLE_CONFIG.fallback, ...userConfig.fallback, }, }; this.stats = { totalInterceptions: 0, interceptionsPerSecond: 0, errorCount: 0, lastInterceptionTime: 0, methodCounts: {}, averageOverhead: 0, isActive: false, }; // Initialize method counts this.config.interceptMethods.forEach((method) => { this.stats.methodCounts[method] = 0; }); // Parse preserve option configuration this.preserveOption = this.parsePreserveOption(this.config.preserveOriginal); // Initialize encryption handler if encryption is enabled this.initializeEncryptionHandler(); } /** * Parse preserve option configuration * Supports both boolean (backward compatibility) and object configuration */ parsePreserveOption(preserveOriginal) { // If it's already a PreserveOption object, use it if (typeof preserveOriginal === "object" && preserveOriginal !== null) { return { ...preserveOriginal, // Use user settings enabled: preserveOriginal.enabled ?? true, mode: preserveOriginal.mode ?? "original", showPrefix: preserveOriginal.showPrefix ?? false, allowDuplication: preserveOriginal.allowDuplication ?? false, separateStreams: preserveOriginal.separateStreams ?? false, onlyUserApp: preserveOriginal.onlyUserApp ?? true, colorize: preserveOriginal.colorize ?? true, }; } // Backward compatibility: convert boolean to PreserveOption if (preserveOriginal === true) { // preserveOriginal: true = show original console output only return types.PRESERVE_PRESETS.development; } else { // preserveOriginal: false = route through logging system return types.PRESERVE_PRESETS.production; } } /** * Initialize encryption handler */ initializeEncryptionHandler() { if (this.config.encryption?.enabled && this.config.encryption.key) { try { this.encryptionHandler = new ConsoleEncryption.ConsoleEncryption(this.config.encryption); this.logger.info("console", "Console encryption handler initialized"); } catch (error) { this.logger.error("console", "Failed to initialize encryption handler", error); this.encryptionHandler = null; } } else { this.encryptionHandler = null; } } /** * Start console interception */ start() { if (!this.config.enabled || this.isIntercepting) { return; } try { this.logger.info("console", "Starting console interception system"); this.backupOriginalConsole(); this.interceptConsoleMethods(); this.isIntercepting = true; this.stats.isActive = true; this.logger.info("console", "Console interception system started successfully"); } catch (error) { this.handleError("Failed to start console interception", error); } } /** * Stop console interception and restore original console */ stop() { if (!this.isIntercepting) { return; } try { this.logger.info("console", "Stopping console interception system"); this.restoreOriginalConsole(); this.isIntercepting = false; this.stats.isActive = false; this.logger.info("console", "Console interception system stopped"); } catch (error) { this.handleError("Failed to stop console interception", error); } } /** * Get current interception statistics */ getStats() { return { ...this.stats }; } /** * Update configuration at runtime */ updateConfig(newConfig) { // Merge configurations properly this.config = { ...this.config, ...newConfig, filters: { ...this.config.filters, ...(newConfig.filters || {}), }, fallback: { ...this.config.fallback, ...(newConfig.fallback || {}), }, }; if (this.isIntercepting) { this.stop(); this.start(); } } /** * Backup original console methods */ backupOriginalConsole() { this.config.interceptMethods.forEach((method) => { const consoleMethod = console[method]; if (typeof consoleMethod === "function") { this.originalConsole[method] = consoleMethod.bind(console); } }); } /** * Restore original console methods */ restoreOriginalConsole() { Object.keys(this.originalConsole).forEach((method) => { console[method] = this.originalConsole[method]; }); } /** * Intercept console methods */ interceptConsoleMethods() { this.config.interceptMethods.forEach((method) => { const originalMethod = this.originalConsole[method]; if (!originalMethod) return; console[method] = (...args) => { this.handleInterceptedCall(method, args, originalMethod); }; }); } /** * Handle intercepted console call */ handleInterceptedCall(method, args, originalMethod) { // Prevent recursion - if we're already processing, just call original if (this.isProcessing || this.processingDepth >= this.maxProcessingDepth) { originalMethod(...args); return; } // Set recursion guards this.isProcessing = true; this.processingDepth++; const startTime = this.config.performanceMode ? performance.now() : 0; try { // Rate limiting check if (!this.checkRateLimit()) { this.handlePreserveDisplay(originalMethod, args); return; } // Create intercepted call object const interceptedCall = { method, args, timestamp: Date.now(), level: this.mapMethodToLogLevel(method), }; // Add source mapping if enabled if (this.config.sourceMapping || this.config.stackTrace) { this.addSourceInformation(interceptedCall); } // Filter the call if (!this.shouldInterceptCall(interceptedCall)) { this.handlePreserveDisplay(originalMethod, args); return; } // Process the intercepted call (async but don't wait to avoid blocking) this.processInterceptedCall(interceptedCall).catch((error) => { this.handleError("Error in async processing", error); }); // Handle preserve display based on new preserve option this.handlePreserveDisplay(originalMethod, args); // Update statistics this.updateStats(method, startTime); } catch (error) { this.handleError(`Error processing intercepted ${method} call`, error); // Fallback to original method this.handlePreserveDisplay(originalMethod, args); } finally { // Always reset recursion guards this.processingDepth--; if (this.processingDepth <= 0) { this.isProcessing = false; this.processingDepth = 0; } } } /** * Handle preserve display based on preserve option configuration */ handlePreserveDisplay(originalMethod, args) { if (!this.preserveOption.enabled) { return; } const mode = this.preserveOption.mode; switch (mode) { case "original": // Show original console output only originalMethod(...args); break; case "intercepted": // Will be handled by processInterceptedCall routing to logger break; case "both": // Show both original and intercepted (for debugging) // Always show original when mode is "both" originalMethod(...args); break; case "none": // No console output at all break; default: // Fallback to original originalMethod(...args); break; } } /** * Determine if logs should be routed to logger system */ shouldRouteToLogger() { if (!this.preserveOption.enabled) { return false; } const mode = this.preserveOption.mode; return mode === "intercepted" || mode === "both"; } /** * Check rate limiting */ checkRateLimit() { const now = Date.now(); const windowSize = 1000; // 1 second // Reset counter if we're in a new window if (now - this.rateLimitWindow >= windowSize) { this.rateLimitCounter = 0; this.rateLimitWindow = now; } this.rateLimitCounter++; return this.rateLimitCounter <= this.config.maxInterceptionsPerSecond; } /** * Map console method to log level */ mapMethodToLogLevel(method) { switch (method) { case "error": return "error"; case "warn": return "warn"; case "info": return "info"; case "debug": return "debug"; case "log": default: return "info"; } } /** * Add source information to intercepted call */ addSourceInformation(call) { if (!this.config.sourceMapping && !this.config.stackTrace) return; try { const stack = new Error().stack; if (stack) { const lines = stack.split("\n"); // Skip the first few lines (Error, this method, handleInterceptedCall) const relevantLine = lines.find((line, index) => index > 3 && !line.includes("ConsoleInterceptor") && !line.includes("node_modules")); if (this.config.sourceMapping && relevantLine) { call.source = relevantLine.trim(); } if (this.config.stackTrace) { call.stackTrace = lines.slice(4).join("\n"); } } } catch (error) { // Silently ignore source mapping errors } } /** * Check if call should be intercepted based on filters */ shouldInterceptCall(call) { const filters = this.config.filters; if (!filters) return true; // Check minimum level if (filters.minLevel) { const levelOrder = { debug: 0, info: 1, warn: 2, error: 3 }; const callLevel = levelOrder[call.level] ?? 1; const minLevel = levelOrder[filters.minLevel] ?? 0; if (callLevel < minLevel) return false; } // Check message length const message = call.args.join(" "); if (filters.maxLength && message.length > filters.maxLength) { return false; } // Check exclude patterns if (filters.excludePatterns?.length) { const source = call.source || call.stackTrace || ""; for (const pattern of filters.excludePatterns) { if (source.includes(pattern) || message.includes(pattern)) { return false; } } } // Check include patterns (if specified, at least one must match) if (filters.includePatterns?.length) { const source = call.source || call.stackTrace || ""; const hasMatch = filters.includePatterns.some((pattern) => source.includes(pattern) || message.includes(pattern)); if (!hasMatch) return false; } return true; } /** * Process intercepted console call through logging system */ async processInterceptedCall(call) { // Temporarily disable processing to prevent recursion const wasProcessing = this.isProcessing; this.isProcessing = true; try { // ALWAYS handle encryption if enabled (background processing) if (this.encryptionHandler && this.config.encryption?.enabled) { try { await this.encryptionHandler.encryptLogEntry(call); } catch (error) { this.logger.warn("console", "Failed to encrypt log entry", error); } } // 🔧 CRITICAL: Respect new preserve option setting if (!this.shouldRouteToLogger()) { // Don't route through logger based on preserve option mode return; } // Route through our logger system with appropriate prefix // DISPLAY: Based on preserve option configuration let displayMessage = call.args.join(" "); // Determine display format only if we're actually displaying const displayMode = this.config.encryption?.displayMode || "readable"; const showEncryptionStatus = this.config.encryption?.showEncryptionStatus || false; // Apply display mode transformations if encryption is enabled if (this.encryptionHandler && this.config.encryption?.enabled && displayMode !== "readable") { // Get the last encrypted entry for display const encryptedLogs = this.encryptionHandler.getEncryptedLogsAsStrings(); if (encryptedLogs.length > 0) { const latestEncrypted = encryptedLogs[encryptedLogs.length - 1]; const encryptedHash = await this.extractEncryptedHash(latestEncrypted); switch (displayMode) { case "encrypted": // Show only the encrypted hash displayMessage = encryptedHash; break; case "both": // Show both readable and encrypted hash displayMessage = `${call.args.join(" ")} [${encryptedHash.substring(0, 32)}...]`; break; } } } // Add encryption status indicator if enabled if (showEncryptionStatus && this.config.encryption?.enabled) { displayMessage = `${displayMessage}`; } const component = "userApp"; // Add metadata if available const metadata = []; if (call.source) { metadata.push(`[${call.source}]`); } // Route through our logging system (only if preserveOriginal is true) switch (call.level) { case "error": this.logger.error(component, displayMessage, ...metadata); break; case "warn": this.logger.warn(component, displayMessage, ...metadata); break; case "debug": this.logger.debug(component, displayMessage, ...metadata); break; default: this.logger.info(component, displayMessage, ...metadata); break; } } catch (error) { // If logging fails and preserveOriginal is true, use original console if (this.config.preserveOriginal) { const originalMethod = this.originalConsole[call.method] || this.originalConsole.log; if (originalMethod) { originalMethod(`[USERAPP] ${call.args.join(" ")}`); } } } finally { // Restore processing state this.isProcessing = wasProcessing; } } /** * Update statistics */ updateStats(method, startTime) { this.stats.totalInterceptions++; this.stats.methodCounts[method] = (this.stats.methodCounts[method] || 0) + 1; this.stats.lastInterceptionTime = Date.now(); // Calculate performance overhead if (this.config.performanceMode && startTime > 0) { const overhead = performance.now() - startTime; this.performanceBuffer.push(overhead); // Keep only last 100 measurements if (this.performanceBuffer.length > 100) { this.performanceBuffer.shift(); } this.stats.averageOverhead = this.performanceBuffer.reduce((a, b) => a + b, 0) / this.performanceBuffer.length; } // Update interceptions per second const now = Date.now(); if (now - this.lastSecondTimestamp >= 1000) { this.stats.interceptionsPerSecond = this.lastSecondInterceptions; this.lastSecondInterceptions = 0; this.lastSecondTimestamp = now; } else { this.lastSecondInterceptions++; } } /** * Handle errors in the interception system */ handleError(message, error) { this.errorCount++; this.stats.errorCount = this.errorCount; const errorMessage = error instanceof Error ? error.message : String(error); switch (this.config.fallback.onError) { case "silent": // Do nothing break; case "throw": throw new Error(`${message}: ${errorMessage}`); case "console": default: // Use original console.error to avoid recursion if (this.originalConsole.error) { this.originalConsole.error(`[ConsoleInterceptor] ${message}:`, errorMessage); } break; } // Check for graceful degradation if (this.config.fallback.gracefulDegradation && this.errorCount >= this.config.fallback.maxErrors) { this.logger.warn("console", "Too many errors in console interception, disabling system"); this.stop(); } } /** * Check if interception is currently active */ isActive() { return this.isIntercepting && this.stats.isActive; } /** * Reset statistics */ resetStats() { this.stats = { totalInterceptions: 0, interceptionsPerSecond: 0, errorCount: 0, lastInterceptionTime: 0, methodCounts: {}, averageOverhead: 0, isActive: this.isIntercepting, }; this.config.interceptMethods.forEach((method) => { this.stats.methodCounts[method] = 0; }); this.errorCount = 0; this.performanceBuffer = []; } // ENCRYPTION METHODS /** * Enable console encryption with a key * @param key - Encryption key (if not provided, will use environment variable) */ enableEncryption(key) { if (!this.config.encryption) { this.config.encryption = { ...types.DEFAULT_CONSOLE_CONFIG.encryption }; } // Preserve existing display mode settings when enabling encryption const existingDisplayMode = this.config.encryption.displayMode; const existingShowStatus = this.config.encryption.showEncryptionStatus; this.config.encryption.enabled = true; // Restore display mode settings if they were previously configured if (existingDisplayMode) { this.config.encryption.displayMode = existingDisplayMode; } if (existingShowStatus !== undefined) { this.config.encryption.showEncryptionStatus = existingShowStatus; } if (key) { this.config.encryption.key = key; } else { // Try to get key from environment this.config.encryption.key = process.env.CONSOLE_ENCRYPTION_KEY || process.env.ENC_SECRET_KEY || this.generateTemporaryKey(); } // Initialize or update encryption handler this.initializeEncryptionHandler(); this.logger.info("console", `Console encryption enabled (displayMode: ${this.config.encryption.displayMode})`); } /** * Disable console encryption */ disableEncryption() { if (this.config.encryption) { this.config.encryption.enabled = false; this.config.encryption.key = undefined; } this.logger.info("console", "Console encryption disabled"); } /** * Set encryption key for console output * @param key - The encryption key to use */ setEncryptionKey(key) { if (!this.config.encryption) { this.config.encryption = { ...types.DEFAULT_CONSOLE_CONFIG.encryption }; } this.config.encryption.key = key; this.logger.info("console", "Console encryption key updated"); } /** * Simple encrypt method - enables encryption with key * Works independently from preserveOriginal setting * @param key - Encryption key */ encrypt(key) { this.enableEncryption(key); } /** * Update encryption display mode * @param displayMode - How to display encrypted logs * @param showEncryptionStatus - Whether to show encryption indicators */ setEncryptionDisplayMode(displayMode, showEncryptionStatus) { if (!this.config.encryption) { this.config.encryption = { ...types.DEFAULT_CONSOLE_CONFIG.encryption }; } this.config.encryption.displayMode = displayMode; if (showEncryptionStatus !== undefined) { this.config.encryption.showEncryptionStatus = showEncryptionStatus; } this.logger.info("console", `Console encryption display mode updated: ${displayMode}`); } /** * Get encrypted console logs (for external transmission) * @returns Array of encrypted log entries */ getEncryptedLogs() { if (!this.encryptionHandler) { throw new Error("Encryption is not enabled or handler is not initialized"); } return this.encryptionHandler.getEncryptedLogsAsStrings(); } /** * Restore console logs from encrypted data * @param encryptedData - Array of encrypted log entries * @param key - Decryption key * @returns Array of decrypted log entries */ async restoreFromEncrypted(encryptedData, key) { try { if (!this.encryptionHandler) { // Create a temporary encryption handler for decryption const tempConfig = { ...types.DEFAULT_CONSOLE_CONFIG.encryption, enabled: true, key, }; this.encryptionHandler = new ConsoleEncryption.ConsoleEncryption(tempConfig); } const decryptedCalls = await this.encryptionHandler.restoreFromEncryptedStrings(encryptedData, key); const decryptedMessages = decryptedCalls.map((call) => call.args.join(" ")); this.logger.info("console", `Restored ${decryptedMessages.length} encrypted log entries`); return decryptedMessages; } catch (error) { this.logger.error("console", "Failed to restore encrypted logs", error); throw error; } } /** * Generate a temporary encryption key (for development) */ generateTemporaryKey() { const key = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); this.logger.warn("console", "Using temporary encryption key. Set CONSOLE_ENCRYPTION_KEY for production."); return key; } /** * Extract encrypted hash from the full encrypted JSON structure */ async extractEncryptedHash(encryptedData) { try { // Parse the encrypted JSON structure const encryptedObj = JSON.parse(encryptedData); // Get the encoding type from config const encoding = this.config.encryption?.encoding || "base64"; // Extract just the encrypted data field and encode it properly const encrypt = index.func(async (out) => { const encr = await hashCore.Hash.createSecureHash(encryptedObj.data, randomCore.SecureRandom.generateSalt(), { outputFormat: out, }); return encr; }); if (encryptedObj.data) { switch (encoding) { case "base64": return await encrypt("base64"); case "hex": return await encrypt("hex"); default: return await encrypt("base64"); } } // Fallback: create a hash of the entire encrypted structure const buffer = Buffer.from(encryptedData); return encoding === "base64" ? buffer.toString("base64") : buffer.toString("hex"); } catch (error) { // If parsing fails, create a simple hash of the raw data const encoding = this.config.encryption?.encoding || "base64"; const buffer = Buffer.from(encryptedData); return encoding === "base64" ? buffer.toString("base64") : buffer.toString("hex"); } } /** * Check if encryption is currently enabled */ isEncryptionEnabled() { return (this.config.encryption?.enabled === true && !!this.config.encryption.key); } /** * Get encryption status and configuration */ getEncryptionStatus() { const encryption = this.config.encryption; return { enabled: encryption?.enabled === true, algorithm: encryption?.algorithm, hasKey: !!encryption?.key, externalLogging: encryption?.externalLogging?.enabled, }; } } exports.ConsoleInterceptor = ConsoleInterceptor; //# sourceMappingURL=ConsoleInterceptor.js.map