'use strict'; const MAX_OPERATION_LENGTH = 64; const MAX_CODE_LENGTH = 64; const MAX_MESSAGE_LENGTH = 240; const REDACTED = '[REDACTED]'; function safeRead(object, key) { try { return object && typeof object === 'object' ? object[key] : undefined; } catch { return undefined; } } function redactKnownPatterns(message) { return message .replace(/\bauthorization\s*:\s*bearer\s+[^\s,;]+/gi, REDACTED) .replace(/\bbearer\s+[^\s,;]+/gi, `Bearer ${REDACTED}`) .replace(/\bcookie\s*:\s*[^\r\n]*/gi, REDACTED) .replace(/\b(va\s*=\s*)[^\s;,]*/gi, `$1${REDACTED}`); } function sanitizeErrorMessage(value, options = {}) { let message = typeof value === 'string' ? value : 'Alta request failed'; const secrets = Array.isArray(options.secrets) ? options.secrets : []; for (const secret of secrets) { if (typeof secret === 'string' && secret.length > 0) { message = message.split(secret).join(REDACTED); } } message = redactKnownPatterns(message) .replace(/[\r\n\t]+/g, ' ') .replace(/[\u0000-\u001f\u007f]/g, '') .replace(/\s{2,}/g, ' ') .trim(); if (!message) message = 'Alta request failed'; return message.slice(0, MAX_MESSAGE_LENGTH); } function includesSecret(value, secrets) { return secrets.some((secret) => typeof secret === 'string' && secret.length > 0 && value.includes(secret)); } function safeIdentifier(value, fallback, maxLength, secrets) { if (typeof value !== 'string' || !/^[A-Za-z0-9_.:-]+$/.test(value)) return fallback; if (includesSecret(value, secrets)) return fallback; return value.slice(0, maxLength); } function safeStatus(error) { const response = safeRead(error, 'response'); const candidate = safeRead(response, 'status') ?? safeRead(error, 'status'); return Number.isInteger(candidate) && candidate >= 100 && candidate <= 599 ? candidate : null; } function formatAltaError(operation, error, options = {}) { const secrets = Array.isArray(options.secrets) ? options.secrets : []; const rawMessage = safeRead(error, 'message'); const rawCode = safeRead(error, 'code'); const timeoutValue = safeRead(error, 'timeout'); const timeout = timeoutValue === true || rawCode === 'ECONNABORTED' || rawCode === 'ETIMEDOUT' || rawCode === 'ALTA_TIMEOUT'; // Deliberately construct a fresh allowlisted object. Never copy or spread the // source error: Axios config/request/headers/data can carry the bearer cookie. return Object.freeze({ operation: safeIdentifier(operation, 'altaRequest', MAX_OPERATION_LENGTH, secrets), code: safeIdentifier(rawCode, 'ALTA_ERROR', MAX_CODE_LENGTH, secrets), status: safeStatus(error), timeout, message: sanitizeErrorMessage(rawMessage, options), }); } module.exports = { MAX_CODE_LENGTH, MAX_MESSAGE_LENGTH, MAX_OPERATION_LENGTH, formatAltaError, formatError: formatAltaError, sanitizeErrorMessage, };