From 4f702cb8a2034c4c620947af37e717eee15ed48e Mon Sep 17 00:00:00 2001 From: 4gray Date: Sat, 18 Jul 2026 21:21:31 +0200 Subject: [PATCH] fix(logging): harden URL and date redaction --- .../src/lib/redact-sensitive-data.spec.ts | 26 +++++++++++++++++++ .../logging/src/lib/redact-sensitive-data.ts | 21 +++++++++------ 2 files changed, 39 insertions(+), 8 deletions(-) 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 e646f491c..1d9e04fd2 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts @@ -100,6 +100,25 @@ describe('redactSensitiveData', () => { expect(output).toContain('401'); }); + it('redacts credentials in non-HTTP stream URL strings', () => { + const username = 'rtsp-user-secret'; + const password = 'rtsp-password-secret'; + const token = 'rtmp-token-secret'; + + const output = serialized( + redactSensitiveData([ + `rtsp://${username}:${password}@stream.example/live?token=${token}&channel=news`, + `Playback failed: rtmp://stream.example/live?password=${password}&channel=sports`, + ]) + ); + + expect(output).not.toContain(username); + expect(output).not.toContain(password); + expect(output).not.toContain(token); + expect(output).toContain('channel=news'); + expect(output).toContain('channel=sports'); + }); + it('redacts a credential from a single-parameter string', () => { const password = 'single-param-password-secret'; const token = 'single-param-token-secret'; @@ -221,6 +240,13 @@ describe('redactSensitiveData', () => { expect(serialized(result)).toContain('[Truncated'); }); + it('serializes invalid dates without throwing', () => { + const invalidDate = new Date(Number.NaN); + + expect(() => redactSensitiveData(invalidDate)).not.toThrow(); + expect(redactSensitiveData([invalidDate])).toEqual(['[Invalid Date]']); + }); + it('preserves repeated non-circular references while still redacting them', () => { const shared = { operation: 'get_profile', diff --git a/libs/shared/logging/src/lib/redact-sensitive-data.ts b/libs/shared/logging/src/lib/redact-sensitive-data.ts index 4c5201f60..40e75b13d 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.ts @@ -155,13 +155,16 @@ function redactUrlStrings( value: string, sanitizeValue: (value: string) => string ): string { - return value.replace(/https?:\/\/[^\s"'<>]+/giu, (candidate) => { - try { - return redactUrl(new URL(candidate), sanitizeValue); - } catch { - return candidate; + return value.replace( + /[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/giu, + (candidate) => { + try { + return redactUrl(new URL(candidate), sanitizeValue); + } catch { + return candidate; + } } - }); + ); } function looksLikeSearchParams(value: string): boolean { @@ -196,7 +199,7 @@ export function redactSensitiveData( } } - if (/^https?:\/\//iu.test(trimmed)) { + if (/^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed)) { try { return truncateString( redactUrl(new URL(trimmed), (entry) => @@ -321,7 +324,9 @@ export function redactSensitiveData( return visitError(input, depth); } if (input instanceof Date) { - return input.toISOString(); + return Number.isNaN(input.getTime()) + ? '[Invalid Date]' + : input.toISOString(); } if (Array.isArray(input)) { const output = input