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
JavaScript
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();