/** * Logger Utilities - Sanitize sensitive data before logging * Created: 2026-03-21 * Purpose: Prevent credential/token/password leakage in logs */ /** * List of sensitive field names that should be redacted */ const SENSITIVE_FIELDS = [ 'password', 'passwd', 'pwd', 'secret', 'token', 'apiKey', 'api_key', 'apikey', 'auth', 'authorization', 'bearer', 'credential', 'credentials', 'key', 'privateKey', 'private_key', 'accessToken', 'access_token', 'refreshToken', 'refresh_token', 'sessionId', 'session_id', 'cookie', 'cookies', 'cert', 'certificate', 'masterKey', 'master_key', 'encryptionKey', 'encryption_key' ]; /** * Recursively sanitize an object by redacting sensitive fields * @param {any} data - Data to sanitize * @param {Array} additionalSensitiveKeys - Additional field names to redact * @returns {any} Sanitized copy of the data */ function sanitizeForLog(data, additionalSensitiveKeys = []) { // Handle null/undefined if (data === null || data === undefined) { return data; } // Handle primitives if (typeof data !== 'object') { return data; } // Handle arrays if (Array.isArray(data)) { return data.map(item => sanitizeForLog(item, additionalSensitiveKeys)); } // Handle objects const sensitiveKeys = [...SENSITIVE_FIELDS, ...additionalSensitiveKeys]; const sanitized = {}; for (const [key, value] of Object.entries(data)) { const lowerKey = key.toLowerCase(); const isSensitive = sensitiveKeys.some(sk => lowerKey.includes(sk.toLowerCase())); if (isSensitive) { // Redact sensitive fields sanitized[key] = '[REDACTED]'; } else if (value && typeof value === 'object') { // Recursively sanitize nested objects sanitized[key] = sanitizeForLog(value, additionalSensitiveKeys); } else { sanitized[key] = value; } } return sanitized; } /** * Redact a credential value for logging (show first/last 4 chars only) * @param {string} value - Credential value * @returns {string} Partially redacted value (e.g., "abcd****xyz") */ function redactCredential(value) { if (!value || typeof value !== 'string') { return '[REDACTED]'; } if (value.length <= 8) { return '[REDACTED]'; } const start = value.slice(0, 4); const end = value.slice(-4); const middle = '*'.repeat(Math.min(value.length - 8, 10)); return `${start}${middle}${end}`; } /** * Create a safe log message object (strips sensitive data) * @param {string} message - Log message * @param {object} data - Data to log * @param {Array} additionalSensitiveKeys - Additional field names to redact * @returns {object} Safe log object */ function safeLog(message, data = {}, additionalSensitiveKeys = []) { return { message, data: sanitizeForLog(data, additionalSensitiveKeys), timestamp: new Date().toISOString() }; } module.exports = { sanitizeForLog, redactCredential, safeLog, SENSITIVE_FIELDS };