"use strict";
/**
 * Sanitizes errors before recording them in OpenTelemetry spans to prevent
 * sensitive information from being sent to DataDog or other observability platforms.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitizeObservabilityMessage = sanitizeObservabilityMessage;
exports.sanitizeObservabilityValue = sanitizeObservabilityValue;
exports.sanitizeError = sanitizeError;
// TODO(alex): check if this is still needed once o11y-refactor is complete
// Patterns that indicate sensitive information
const SENSITIVE_PATTERNS = [
    // Tokens and keys - capture groups to preserve prefixes
    /(\bbearer\s+)[a-zA-Z0-9\-._~+/]+=*/gi,
    /(\bbasic\s+)\S+/gi, // Basic auth can contain any non-whitespace characters
    /(\bjwt\s+)[a-zA-Z0-9\-._~+/]+=*/gi,
    /(\btoken[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
    /(\bapi[_\s]?key[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
    /(\baccess[_\s]?token[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
    /(\brefresh[_\s]?token[:\s=]+)[a-zA-Z0-9\-._~+/]{16,}=*/gi,
    // JWT pattern (base64.base64.base64) - standalone tokens
    /\b[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\.[A-Za-z0-9-_]{20,}\b/g
];
// Fields to completely remove from error objects
const COMPLETELY_REDACTED_FIELDS = [
    'token',
    'accesstoken',
    'access_token',
    'refreshtoken',
    'refresh_token',
    'jwt',
    'bearer',
    'apikey',
    'api_key',
    'secret',
    'password',
    'passwd',
    'key',
    'private_key',
    'privatekey',
    'credentials',
    'auth',
    'session', //TODO(alex): confirm whether we can remove this
    'cookie'
];
// Fields that should have their values sanitized but structure preserved
const VALUE_SANITIZED_FIELDS = ['authorization', 'headers'];
/**
 * Sanitizes a message string by replacing sensitive patterns
 */
function sanitizeMessage(message) {
    if (!message)
        return message;
    let sanitized = message;
    for (const pattern of SENSITIVE_PATTERNS) {
        if (pattern.global) {
            pattern.lastIndex = 0; // Reset regex state
        }
        if (pattern.source.includes('(')) {
            // Pattern has capture groups - preserve prefix
            sanitized = sanitized.replace(pattern, '$1[REDACTED]');
        }
        else {
            // Pattern matches entire sensitive value
            sanitized = sanitized.replace(pattern, '[REDACTED]');
        }
    }
    return sanitized;
}
/**
 * Sanitizes a stack trace by replacing sensitive patterns
 */
function sanitizeStackTrace(stack) {
    if (!stack)
        return stack;
    return sanitizeMessage(stack);
}
/**
 * Recursively sanitizes an object by removing/redacting sensitive fields
 */
function sanitizeObject(obj, depth = 0) {
    if (depth > 5) {
        // Prevent infinite recursion
        return '[MAX_DEPTH_REACHED]';
    }
    if (obj === null || obj === undefined) {
        return obj;
    }
    if (typeof obj === 'string') {
        return sanitizeMessage(obj);
    }
    if (typeof obj === 'number' || typeof obj === 'boolean') {
        return obj;
    }
    if (typeof obj !== 'object') {
        return obj;
    }
    if (Array.isArray(obj)) {
        return obj.map((item) => sanitizeObject(item, depth + 1));
    }
    const sanitized = {};
    for (const [key, value] of Object.entries(obj)) {
        const lowerKey = key.toLowerCase();
        // Check for value-sanitized fields first (more specific)
        if (VALUE_SANITIZED_FIELDS.some((field) => lowerKey === field)) {
            if (typeof value === 'string') {
                sanitized[key] = sanitizeMessage(value);
            }
            else {
                sanitized[key] = sanitizeObject(value, depth + 1);
            }
            continue;
        }
        // Then check for completely redacted fields
        if (COMPLETELY_REDACTED_FIELDS.some((field) => lowerKey === field || lowerKey.includes(field))) {
            sanitized[key] = '[REDACTED]';
            continue;
        }
        // Special handling for common error properties
        if (key === 'message') {
            sanitized[key] = sanitizeMessage(value);
        }
        else if (key === 'stack') {
            sanitized[key] = sanitizeStackTrace(value);
        }
        else {
            sanitized[key] = sanitizeObject(value, depth + 1);
        }
    }
    return sanitized;
}
// Public wrappers for general observability log sanitization
function sanitizeObservabilityMessage(message) {
    return sanitizeMessage(message);
}
function sanitizeObservabilityValue(value) {
    return sanitizeObject(value);
}
/**
 * Sanitizes an error object for safe recording in OpenTelemetry spans
 */
function sanitizeError(error) {
    if (!error) {
        return error;
    }
    // If it's already a sanitized error, return as-is
    const errorWithSanitized = error;
    if (errorWithSanitized._sanitized) {
        return errorWithSanitized;
    }
    let sanitized;
    if (error instanceof Error) {
        // Create a new error with sanitized properties
        sanitized = new Error(sanitizeMessage(error.message));
        sanitized.name = error.name;
        sanitized.stack = sanitizeStackTrace(error.stack || '');
        // Copy and sanitize all enumerable properties
        Object.keys(error).forEach((prop) => {
            if (prop !== 'message' && prop !== 'name' && prop !== 'stack') {
                const lowerProp = prop.toLowerCase();
                const errorWithProps = error;
                // Check for value-sanitized fields first (more specific)
                if (VALUE_SANITIZED_FIELDS.some((field) => lowerProp === field)) {
                    if (typeof errorWithProps[prop] === 'string') {
                        sanitized[prop] = sanitizeMessage(errorWithProps[prop]);
                    }
                    else {
                        sanitized[prop] = sanitizeObject(errorWithProps[prop]);
                    }
                }
                else if (COMPLETELY_REDACTED_FIELDS.some((field) => lowerProp === field || lowerProp.includes(field))) {
                    sanitized[prop] = '[REDACTED]';
                }
                else {
                    sanitized[prop] = sanitizeObject(errorWithProps[prop]);
                }
            }
        });
        // Also check non-enumerable properties that might have been added
        Object.getOwnPropertyNames(error).forEach((prop) => {
            if (prop !== 'message' && prop !== 'name' && prop !== 'stack' && !Object.prototype.propertyIsEnumerable.call(error, prop)) {
                const lowerProp = prop.toLowerCase();
                const errorWithProps = error;
                // Check for value-sanitized fields first (more specific)
                if (VALUE_SANITIZED_FIELDS.some((field) => lowerProp === field)) {
                    if (typeof errorWithProps[prop] === 'string') {
                        sanitized[prop] = sanitizeMessage(errorWithProps[prop]);
                    }
                    else {
                        sanitized[prop] = sanitizeObject(errorWithProps[prop]);
                    }
                }
                else if (COMPLETELY_REDACTED_FIELDS.some((field) => lowerProp === field || lowerProp.includes(field))) {
                    sanitized[prop] = '[REDACTED]';
                }
                else {
                    sanitized[prop] = sanitizeObject(errorWithProps[prop]);
                }
            }
        });
    }
    else {
        // For non-Error objects, sanitize the entire object
        const sanitizedObj = sanitizeObject(error);
        sanitized = sanitizedObj;
    }
    // Mark as sanitized to prevent double-processing
    sanitized._sanitized = true;
    return sanitized;
}
//# sourceMappingURL=errorSanitizer.js.map