All files / logging log-sanitizer.ts

100% Statements 26/26
100% Branches 17/17
100% Functions 5/5
100% Lines 24/24

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87      7x                         7x           224x                 7x   39x 1x     38x 10x       28x 1x       27x 1x               26x 2x       25x 25x 35x 17x   18x       25x           7x 10x 6x     4x          
/**
 * Patterns for detecting sensitive data in log entries
 */
const SENSITIVE_PATTERNS = [
    /password/i,
    /token/i,
    /secret/i,
    /authorization/i,
    /api_key/i,
    /apikey/i,
    /api-key/i,
    /bearer/i,
    /credential/i,
    /private/i,
];
 
const REDACTED = '[REDACTED]';
 
/**
 * Check if a key name matches any sensitive pattern
 */
function isSensitiveKey(key: string): boolean {
    return SENSITIVE_PATTERNS.some(pattern => pattern.test(key));
}
 
/**
 * Recursively sanitize an object, masking sensitive values
 * @param value - Value to sanitize (can be any type)
 * @param depth - Current recursion depth (prevents infinite loops)
 * @returns Sanitized copy of the value
 */
export function sanitizeLogData(value: unknown, depth = 0): unknown {
    // Prevent infinite recursion
    if (depth > 10) {
        return '[MAX_DEPTH_EXCEEDED]';
    }
 
    if (!value || typeof value !== 'object') {
        return value;
    }
 
    // Handle Date
    if (value instanceof Date) {
        return value;
    }
 
    // Handle Error
    if (value instanceof Error) {
        return {
            name: value.name,
            message: value.message,
            stack: value.stack,
        };
    }
 
    // Handle arrays
    if (Array.isArray(value)) {
        return value.map(item => sanitizeLogData(item, depth + 1));
    }
 
    // Handle objects
    const sanitized: Record<string, unknown> = {};
    for (const [key, val] of Object.entries(value)) {
        if (isSensitiveKey(key)) {
            sanitized[key] = REDACTED;
        } else {
            sanitized[key] = sanitizeLogData(val, depth + 1);
        }
    }
 
    return sanitized;
}
 
/**
 * Sanitize a log entry, masking sensitive data in the data field
 */
export function sanitizeLogEntry<T extends { data?: unknown }>(entry: T): T {
    if (entry.data === undefined) {
        return entry;
    }
 
    return {
        ...entry,
        data: sanitizeLogData(entry.data),
    };
}