'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { formatAltaError, sanitizeErrorMessage, } = require('../src/error-redaction'); const SENTINEL = 'HERMES_SENTINEL_SECRET'; function assertNoSecret(value) { const serialized = JSON.stringify(value); assert.equal(serialized.includes(SENTINEL), false, serialized); assert.equal(serialized.includes('Cookie'), false, serialized); assert.equal(serialized.includes('Authorization'), false, serialized); } test('formats only an allowlist of bounded safe fields', () => { const error = new Error('request failed safely'); error.code = 'ECONNRESET'; error.timeout = false; error.response = { status: 502 }; const formatted = formatAltaError('getDevices', error, { secrets: [SENTINEL] }); assert.deepEqual(formatted, { operation: 'getDevices', code: 'ECONNRESET', status: 502, timeout: false, message: 'request failed safely', }); assert.deepEqual(Object.keys(formatted), ['operation', 'code', 'status', 'timeout', 'message']); }); test('never serializes Axios config, request, headers, URL, body, cause or response data', () => { const error = new Error(`failed with va=${SENTINEL}`); error.code = 'ERR_BAD_RESPONSE'; error.config = { url: `https://evil.example/?token=${SENTINEL}`, headers: { Cookie: `va=${SENTINEL}`, Authorization: `Bearer ${SENTINEL}` }, data: { token: SENTINEL }, }; error.request = { rawHeaders: `Cookie: va=${SENTINEL}` }; error.response = { status: 401, headers: { 'set-cookie': `va=${SENTINEL}` }, data: { message: SENTINEL, nested: { secret: SENTINEL } }, }; error.cause = { message: SENTINEL, headers: { Cookie: SENTINEL } }; const formatted = formatAltaError('getDevices', error, { secrets: [SENTINEL] }); assertNoSecret(formatted); assert.deepEqual(Object.keys(formatted), ['operation', 'code', 'status', 'timeout', 'message']); assert.equal(formatted.message, 'failed with va=[REDACTED]'); }); test('redacts cookie and bearer patterns without needing the exact secret', () => { for (const message of [ `Cookie: va=${SENTINEL}; other=value`, `va=${SENTINEL}`, `Authorization: Bearer ${SENTINEL}`, `Bearer ${SENTINEL}`, ]) { const clean = sanitizeErrorMessage(message); assertNoSecret({ message: clean }); } }); test('bounds and sanitizes operation, code, status and message', () => { const error = { message: `line one\r\nCookie: va=${SENTINEL} ${'x'.repeat(1000)}`, code: 'BAD CODE WITH SPACES AND SECRET_' + SENTINEL, response: { status: 9999 }, }; const formatted = formatAltaError(`unsafe operation ${SENTINEL}`, error, { secrets: [SENTINEL] }); assertNoSecret(formatted); assert.ok(formatted.operation.length <= 64); assert.equal(formatted.code, 'ALTA_ERROR'); assert.equal(formatted.status, null); assert.ok(formatted.message.length <= 240); assert.equal(/[\r\n]/.test(formatted.message), false); }); test('handles hostile accessors, cycles and non-error values without leaking or throwing', () => { const hostile = {}; Object.defineProperty(hostile, 'message', { get() { throw new Error(SENTINEL); } }); Object.defineProperty(hostile, 'code', { get() { throw new Error(SENTINEL); } }); hostile.self = hostile; const formatted = formatAltaError('request', hostile, { secrets: [SENTINEL] }); assertNoSecret(formatted); assert.equal(formatted.message, 'Alta request failed'); assert.equal(formatted.code, 'ALTA_ERROR'); assertNoSecret(formatAltaError('request', SENTINEL, { secrets: [SENTINEL] })); assertNoSecret(formatAltaError('request', null, { secrets: [SENTINEL] })); }); test('does not leak a supplied secret through valid-looking operation or code fields', () => { const formatted = formatAltaError(SENTINEL, { code: SENTINEL, message: 'failed', }, { secrets: [SENTINEL] }); assertNoSecret(formatted); assert.equal(formatted.operation, 'altaRequest'); assert.equal(formatted.code, 'ALTA_ERROR'); });