fix(logging): close credential redaction gaps

This commit is contained in:
4gray committed 2026-07-18 20:06:15 +02:00
1 parent 002f559b9d
commit 535dc809f0
2 files changed
+61 -2

No files matched your search

@@ -48,6 +48,29 @@ describe('redactSensitiveData', () => {
});
});
it('redacts Stalker identity credentials while retaining request diagnostics', () => {
const identitySecrets = {
sn: 'stalker-sn-secret',
device_id: 'stalker-device-id-secret',
device_id2: 'stalker-device-id2-secret',
signature: 'stalker-signature-secret',
signature2: 'stalker-signature2-secret',
};
const result = redactSensitiveData({
action: 'get_profile',
requestId: 'stalker-request-id',
params: identitySecrets,
});
const output = serialized(result);
for (const secret of Object.values(identitySecrets)) {
expect(output).not.toContain(secret);
}
expect(output).toContain('get_profile');
expect(output).toContain('stalker-request-id');
});
it('redacts credentials embedded in URL, URLSearchParams, errors, and serialized strings', () => {
const url = new URL(
`https://user:pass@example.com/live?password=${TEST_SECRETS[6]}&token=${TEST_SECRETS[7]}&action=get_live_streams`
@@ -77,6 +100,23 @@ describe('redactSensitiveData', () => {
expect(output).toContain('401');
});
it('redacts a credential from a single-parameter string', () => {
const password = 'single-param-password-secret';
const token = 'single-param-token-secret';
const output = serialized(
redactSensitiveData([
`password=${password}`,
`token=${token}`,
'operation=get_profile',
])
);
expect(output).not.toContain(password);
expect(output).not.toContain(token);
expect(output).toContain('get_profile');
});
it('does not repeat a redacted Error message secret in its stack', () => {
const secret = 'error-stack-password-secret';
const error = new Error(`password=${secret}&operation=get_profile`);
@@ -139,6 +179,20 @@ describe('redactSensitiveData', () => {
expect(output).toContain(ordinaryLiveUrl);
});
it('redacts Xtream path credentials when no resource segment follows', () => {
const username = 'terminal-xtream-user-secret';
const password = 'terminal-xtream-password-secret';
const output = serialized(
redactSensitiveData(
new URL(`https://example.com/live/${username}/${password}`)
)
);
expect(output).not.toContain(username);
expect(output).not.toContain(password);
});
it('does not mutate input and safely bounds cycles, depth, arrays, objects, and strings', () => {
const input: Record<string, unknown> = {
status: 'ok',
@@ -13,6 +13,8 @@ const SENSITIVE_KEY_NAMES = new Set([
'authorization',
'cookie',
'credentials',
'deviceid',
'deviceid2',
'login',
'mac',
'macaddress',
@@ -21,6 +23,9 @@ const SENSITIVE_KEY_NAMES = new Set([
'pwd',
'secret',
'setcookie',
'signature',
'signature2',
'sn',
'token',
'username',
]);
@@ -127,7 +132,7 @@ function redactUrl(
}
const pathSegments = redacted.pathname.split('/');
for (let index = 0; index < pathSegments.length - 3; index += 1) {
for (let index = 0; index < pathSegments.length - 2; index += 1) {
if (XTREAM_CREDENTIAL_PATH_SEGMENTS.has(pathSegments[index])) {
pathSegments[index + 1] = REDACTED_VALUE;
pathSegments[index + 2] = REDACTED_VALUE;
@@ -158,7 +163,7 @@ function redactUrlStrings(
}
function looksLikeSearchParams(value: string): boolean {
return /^[^=&\s]+=[^&]*(?:&[^=&\s]+=[^&]*)+$/u.test(value);
return /^[^=&\s]+=[^&]*(?:&[^=&\s]+=[^&]*)*$/u.test(value);
}
export function redactSensitiveData(