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