diff --git a/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts b/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts index 100977237..e646f491c 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts @@ -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 = { status: 'ok', diff --git a/libs/shared/logging/src/lib/redact-sensitive-data.ts b/libs/shared/logging/src/lib/redact-sensitive-data.ts index c476ded6d..4f5e9f8ef 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.ts @@ -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(