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
JavaScript
'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