UNPKG

ssh-bridge-ai

Version:

AI-Powered SSH Tool with Bulletproof Connections & Enterprise Sandbox Security + Cursor-like Confirmation - Enable AI assistants to securely SSH into your servers with persistent sessions, keepalive, automatic recovery, sandbox command testing, and user c

225 lines (198 loc) 6.16 kB
const chalk = require('chalk'); /** * Centralized logging utility with security features */ class Logger { constructor() { // SECURITY: Avoid circular dependency by not creating Config instance here this.logLevels = { debug: 0, info: 1, warn: 2, error: 3, }; // Get log level from environment variable directly this.logLevel = process.env.SSHBRIDGE_LOG_LEVEL || 'info'; } /** * Log a message at the specified level * @param {string} level - Log level * @param {string} message - Message to log * @param {Object} data - Optional data to log * @param {boolean} sensitive - Whether data contains sensitive information */ log(level, message, data = null, sensitive = false) { const currentLevel = this.logLevels[this.logLevel] || 1; const messageLevel = this.logLevels[level] || 1; if (messageLevel < currentLevel) { return; // Skip logging at this level } const timestamp = new Date().toISOString(); const sanitizedMessage = this.sanitizeMessage(message); // Format the log entry const logEntry = this.formatLogEntry(level, sanitizedMessage, timestamp); // Log to console with appropriate colors this.logToConsole(level, logEntry); // Log data if provided and safe if (data && !sensitive) { const sanitizedData = this.sanitizeData(data); console.log(chalk.gray(`[${timestamp}] [${level.toUpperCase()}] Data:`, sanitizedData)); } } /** * Log debug message * @param {string} message - Message to log * @param {Object} data - Optional data */ debug(message, data = null) { this.log('debug', message, data); } /** * Log info message * @param {string} message - Message to log * @param {Object} data - Optional data */ info(message, data = null) { this.log('info', message, data); } /** * Log warning message * @param {string} message - Message to log * @param {Object} data - Optional data */ warn(message, data = null) { this.log('warn', message, data); } /** * Log error message * @param {string} message - Message to log * @param {Object} data - Optional data */ error(message, data = null) { this.log('error', message, data); } /** * Log sensitive information (only if explicitly enabled) * @param {string} message - Message to log * @param {Object} data - Sensitive data */ logSensitive(message, data) { // SECURITY: Only log sensitive data if explicitly enabled via environment if (process.env.SSHBRIDGE_LOG_SENSITIVE === 'true') { this.log('info', message, data, true); } else { // Log message without sensitive data this.log('info', message); } } /** * Format log entry * @param {string} level - Log level * @param {string} message - Sanitized message * @param {string} timestamp - ISO timestamp * @returns {string} - Formatted log entry */ formatLogEntry(level, message, timestamp) { return `[${timestamp}] [${level.toUpperCase()}] ${message}`; } /** * Log to console with appropriate colors * @param {string} level - Log level * @param {string} logEntry - Formatted log entry */ logToConsole(level, logEntry) { switch (level) { case 'debug': console.log(chalk.gray(logEntry)); break; case 'info': console.log(chalk.blue(logEntry)); break; case 'warn': console.log(chalk.yellow(logEntry)); break; case 'error': console.log(chalk.red(logEntry)); break; default: console.log(logEntry); } } /** * Sanitize message to remove sensitive information * @param {string} message - Message to sanitize * @returns {string} - Sanitized message */ sanitizeMessage(message) { if (!message || typeof message !== 'string') { return message; } // Remove potential sensitive data patterns const sensitivePatterns = [ /password\s*[:=]\s*\S+/gi, /key\s*[:=]\s*\S+/gi, /token\s*[:=]\s*\S+/gi, /secret\s*[:=]\s*\S+/gi, /api[_-]?key\s*[:=]\s*\S+/gi, /private[_-]?key\s*[:=]\s*\S+/gi, /ssh[_-]?key\s*[:=]\s*\S+/gi, ]; let sanitized = message; for (const pattern of sensitivePatterns) { sanitized = sanitized.replace(pattern, (match) => { const parts = match.split(/[:=]/); if (parts.length === 2) { return `${parts[0]}: [REDACTED]`; } return match; }); } return sanitized; } /** * Sanitize data object to remove sensitive information * @param {Object} data - Data to sanitize * @returns {Object} - Sanitized data */ sanitizeData(data) { if (!data || typeof data !== 'object') { return data; } const sanitized = { ...data }; const sensitiveKeys = [ 'password', 'key', 'token', 'secret', 'api_key', 'apiKey', 'private_key', 'privateKey', 'ssh_key', 'sshKey', ]; for (const key of sensitiveKeys) { if (sanitized.hasOwnProperty(key)) { sanitized[key] = '[REDACTED]'; } } return sanitized; } /** * Log security event * @param {string} event - Security event description * @param {Object} details - Event details */ securityEvent(event, details = {}) { this.error(`SECURITY EVENT: ${event}`, details); // In production, you might want to send this to a security monitoring service if (process.env.NODE_ENV === 'production') { // TODO: Implement security event reporting console.log(chalk.red.bold('🚨 SECURITY EVENT LOGGED')); } } /** * Log performance metrics * @param {string} operation - Operation name * @param {number} duration - Duration in milliseconds * @param {Object} metadata - Additional metadata */ performance(operation, duration, metadata = {}) { const level = duration > 1000 ? 'warn' : 'debug'; this.log(level, `Performance: ${operation} took ${duration}ms`, metadata); } } // Export singleton instance module.exports = new Logger();