const REDACTION = '[REDACTED]'; const REDACT_KEY_RE = /(authorization|x-api-key|api[-_]?key|access_token|refresh_token|client_secret|private_key|set-cookie|cookie|password|secret)/i; const EMAIL_KEY_RE = /email/i; const IP_KEY_RE = /(^ip$|ip_address|remote_address|x-forwarded-for)/i; function maskEmail(value) { if (typeof value !== 'string') return value; return value.replace(/([A-Za-z0-9._%+-])([A-Za-z0-9._%+-]*)(@[A-Za-z0-9.-]+\.[A-Za-z]{2,})/g, '$1***$3'); } function maskIp(value) { if (typeof value !== 'string') return value; let masked = value.replace(/\b(\d{1,3}\.\d{1,3}\.\d{1,3})\.\d{1,3}\b/g, '$1.xxx'); masked = masked.replace(/\b([A-Fa-f0-9]{0,4}:){2,7}[A-Fa-f0-9]{0,4}\b/g, '****'); return masked; } function maskTokensInString(value) { if (typeof value !== 'string') return value; let masked = value.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+\b/g, 'Bearer ' + REDACTION); masked = masked.replace(/\b(api_key|apikey|access_token|refresh_token|client_secret|password)=([^\s&]+)/gi, '$1=' + REDACTION); return masked; } function sanitizeString(value) { if (typeof value !== 'string') return value; let masked = value; masked = maskTokensInString(masked); masked = maskEmail(masked); masked = maskIp(masked); return masked; } function sanitizeValue(value, key, seen) { if (value === null || value === undefined) return value; if (key && REDACT_KEY_RE.test(key)) { return REDACTION; } if (typeof value === 'string') { if (key && EMAIL_KEY_RE.test(key)) { return maskEmail(value); } if (key && IP_KEY_RE.test(key)) { return maskIp(value); } return sanitizeString(value); } if (typeof value === 'number' || typeof value === 'boolean') { return value; } if (Array.isArray(value)) { return value.map(item => sanitizeValue(item, key, seen)); } if (typeof value === 'object') { return sanitizeObject(value, seen); } return value; } function sanitizeObject(value, seen) { if (!value || typeof value !== 'object') return value; if (!seen) seen = new WeakSet(); if (seen.has(value)) return '[Circular]'; seen.add(value); const output = Array.isArray(value) ? [] : {}; for (const [key, val] of Object.entries(value)) { output[key] = sanitizeValue(val, key, seen); } return output; } export function sanitizeForLog(value) { return sanitizeValue(value, null, new WeakSet()); } export function sanitizeLogMessage(message) { if (typeof message !== 'string') return message; return sanitizeString(message); }