import { getLogger } from './logging.js'; const logger = getLogger(); /** * Contains utility functions for secure configuration handling */ /** * Property names that should be considered sensitive */ const SENSITIVE_PROPERTIES = [ 'key', 'secret', 'password', 'token', 'credential', 'auth', 'private' ]; /** * Mask sensitive values for logging/display * @param value The value to mask * @param showLength Whether to include the length of masked value * @returns Masked string representation */ export function maskSensitiveValue(value: string, showLength = true): string { if (!value) { return ''; } if (value.length <= 4) { return '****'; } // For longer values, show first and last 2 characters const masked = `${value.substring(0, 2)}...${value.substring(value.length - 2)}`; return showLength ? `${masked} (${value.length} chars)` : masked; } /** * Check if a property name should be considered sensitive * @param propertyName The name of the property to check * @returns Whether the property should be masked */ export function isSensitiveProperty(propertyName: string): boolean { const lowerName = propertyName.toLowerCase(); return SENSITIVE_PROPERTIES.some(term => lowerName.includes(term)); } /** * Create a sanitized copy of an object with sensitive values masked * @param obj Object containing potentially sensitive information * @returns Safely sanitized copy for logging */ export function sanitizeObjectForLogging(obj: Record): Record { if (!obj || typeof obj !== 'object') { return obj; } const result: Record = {}; for (const [key, value] of Object.entries(obj)) { // Handle nested objects recursively if (value && typeof value === 'object' && !Array.isArray(value)) { result[key] = sanitizeObjectForLogging(value); continue; } // Mask sensitive string values if (typeof value === 'string' && isSensitiveProperty(key)) { result[key] = maskSensitiveValue(value); continue; } // Pass through other values unchanged result[key] = value; } return result; } /** * Safely log configuration without exposing sensitive values * @param message Log message * @param config Configuration object with potentially sensitive values * @param level Optional log level, defaults to 'info' */ export function logConfigSafely( message: string, config: Record, level: 'debug' | 'info' | 'warn' | 'error' = 'info' ): void { const sanitizedConfig = sanitizeObjectForLogging(config); switch (level) { case 'debug': logger.debug(message, sanitizedConfig); break; case 'warn': logger.warn(message, sanitizedConfig); break; case 'error': logger.error(message, sanitizedConfig); break; case 'info': default: logger.info(message, sanitizedConfig); break; } } /** * Redact sensitive information from error messages * @param message Error message that may contain sensitive information * @returns Redacted message */ export function redactSensitiveInfoFromErrorMessage(message: string): string { if (!message) { return message; } // Redact common patterns in error messages const patterns = [ // AWS Access Key pattern { regex: /(?:access[_\s]?key)[_\s]?(?:id)?[:=\s]\s*["']?([A-Z0-9]{16,})["']?/gi, replacement: (_: string, p1: string) => `access key: "${maskSensitiveValue(p1, false)}"` }, // AWS Secret Key pattern (partial, since these don't have a fixed format) { regex: /(?:secret[_\s]?(?:access)?[_\s]?key)[:=\s]\s*["']?([A-Za-z0-9+/]{8,})["']?/gi, replacement: (_: string, p1: string) => `secret key: "${maskSensitiveValue(p1, false)}"` }, // Session token pattern { regex: /(?:session[_\s]?token)[:=\s]\s*["']?([A-Za-z0-9+/=]{8,})["']?/gi, replacement: (_: string, p1: string) => `session token: "${maskSensitiveValue(p1, false)}"` }, // URL with embedded credentials // eslint-disable-next-line no-useless-escape { regex: /(https?:\/\/)([^:]+:[^@]+)@([^:\/\s]+)/gi, replacement: (_: string, protocol: string, creds: string, host: string) => `${protocol}${maskSensitiveValue(creds, false)}@${host}` } ]; let redactedMessage = message; // Apply each redaction pattern for (const pattern of patterns) { redactedMessage = redactedMessage.replace(pattern.regex, pattern.replacement); } return redactedMessage; }