/** * Utilities for sanitizing sensitive data before logging */ import { IS_DEV } from './config'; const SENSITIVE_KEYS = [ 'token', 'password', 'apiKey', 'api_key', 'apikey', 'secret', 'auth', 'authorization', 'session', 'sessionString', 'session_string', 'credentials', 'privateKey', 'private_key', 'accessToken', 'access_token', 'refreshToken', 'refresh_token', ]; const SENSITIVE_PATTERNS = [/password/i, /secret/i, /token/i, /key/i, /auth/i, /credential/i]; /** * Check if a key name suggests sensitive data */ function isSensitiveKey(key: string): boolean { const lowerKey = key.toLowerCase(); return ( SENSITIVE_KEYS.some(sk => lowerKey.includes(sk)) || SENSITIVE_PATTERNS.some(pattern => pattern.test(key)) ); } /** * Sanitize an object by redacting sensitive values */ function sanitizeObject(obj: unknown, depth = 0): unknown { if (depth > 5) { return '[Max depth reached]'; } if (obj === null || obj === undefined) { return obj; } if (typeof obj !== 'object') { return obj; } if (Array.isArray(obj)) { return obj.map(item => sanitizeObject(item, depth + 1)); } const sanitized: Record = {}; for (const [key, value] of Object.entries(obj)) { if (isSensitiveKey(key)) { sanitized[key] = '[REDACTED]'; } else if (typeof value === 'object' && value !== null) { sanitized[key] = sanitizeObject(value, depth + 1); } else { sanitized[key] = value; } } return sanitized; } /** * Sanitize error objects, extracting only safe information */ export function sanitizeError(error: unknown): unknown { if (error instanceof Error) { return { name: error.name, message: error.message, stack: IS_DEV ? error.stack : undefined }; } if (typeof error === 'object' && error !== null) { return sanitizeObject(error); } return error; } /** * Sanitize data for logging - removes sensitive fields and limits size */ export function sanitizeForLogging(data: unknown): unknown { if (data === null || data === undefined) { return data; } // For errors, use specialized sanitization if (data instanceof Error) { return sanitizeError(data); } // For objects, sanitize sensitive keys if (typeof data === 'object') { const sanitized = sanitizeObject(data); // If it's a large object, only show metadata const jsonStr = JSON.stringify(sanitized); if (jsonStr.length > 1000) { return { ...(typeof sanitized === 'object' && sanitized !== null ? { _truncated: true, _size: jsonStr.length } : {}), _preview: jsonStr.substring(0, 200) + '...', }; } return sanitized; } return data; } /** * Create a safe log data object that only includes metadata */ export function createSafeLogData( metadata: Record, sensitiveData?: unknown ): Record { const safe: Record = { ...metadata }; if (sensitiveData !== undefined) { safe.hasData = true; safe.dataSize = typeof sensitiveData === 'string' ? sensitiveData.length : JSON.stringify(sensitiveData).length; // Only include sanitized preview for small objects const sanitized = sanitizeForLogging(sensitiveData); const jsonStr = JSON.stringify(sanitized); if (jsonStr.length <= 500) { safe.data = sanitized; } else { safe.dataPreview = jsonStr.substring(0, 200) + '...'; } } else { safe.hasData = false; } return safe; }