fix: add main-process Alta session boundary
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
'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,
|
||||
};
|
||||
Reference in New Issue
Block a user