Files
Alta-Proxy-Tool/test/error-redaction.test.js
T

110 lines
3.9 KiB
JavaScript

'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');
});