diff --git a/apps/electron-backend/src/app/services/debug-trace.ts b/apps/electron-backend/src/app/services/debug-trace.ts index 86fe7669c..f9a0037ae 100644 --- a/apps/electron-backend/src/app/services/debug-trace.ts +++ b/apps/electron-backend/src/app/services/debug-trace.ts @@ -1,3 +1,5 @@ +import { redactSensitiveData } from '@iptvnator/shared/logging'; + const TRACE_ENV_TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']); const TRACE_PREFIX = '[IPTVnator Trace]'; const MAX_TRACE_ARRAY_ITEMS = 5; @@ -166,8 +168,7 @@ export function trace(scope: string, message: string, payload?: unknown): void { console.log( `${TRACE_PREFIX}[${scope}] ${message} ${safeStringifyForTrace( - summarizeForTrace(redactSensitiveData(payload)) + summarizeForTrace(payload) )}` ); } -import { redactSensitiveData } from '@iptvnator/shared/logging'; 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 29d97f0b0..9d33e63a8 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.spec.ts @@ -121,4 +121,24 @@ describe('redactSensitiveData', () => { expect(serialized(result)).not.toContain(TEST_SECRETS[2]); expect(serialized(result)).toContain('[Truncated'); }); + + it('preserves repeated non-circular references while still redacting them', () => { + const shared = { + operation: 'get_profile', + password: TEST_SECRETS[2], + }; + + const result = redactSensitiveData({ first: shared, second: shared }); + + expect(result).toEqual({ + first: { + operation: 'get_profile', + password: REDACTED_VALUE, + }, + second: { + operation: 'get_profile', + password: REDACTED_VALUE, + }, + }); + }); }); diff --git a/libs/shared/logging/src/lib/redact-sensitive-data.ts b/libs/shared/logging/src/lib/redact-sensitive-data.ts index 758d969bf..a73d5bd47 100644 --- a/libs/shared/logging/src/lib/redact-sensitive-data.ts +++ b/libs/shared/logging/src/lib/redact-sensitive-data.ts @@ -277,43 +277,51 @@ export function redactSensitiveData( } seen.add(object); - if (input instanceof URL) { - return redactUrl(input, (entry) => visitString(entry, depth + 1)); - } - if (input instanceof URLSearchParams) { - return redactSearchParams(input, (entry) => - visitString(entry, depth + 1) - ).toString(); - } - if (input instanceof Error) { - return visitError(input, depth); - } - if (input instanceof Date) { - return input.toISOString(); - } - if (Array.isArray(input)) { - const output = input - .slice(0, resolved.maxArrayItems) - .map((entry) => visit(entry, depth + 1)); - if (input.length > resolved.maxArrayItems) { - output.push( - `[Truncated ${input.length - resolved.maxArrayItems} items]` + try { + if (input instanceof URL) { + return redactUrl(input, (entry) => + visitString(entry, depth + 1) ); } - return output; - } - if (input instanceof Map) { - const entries: Record = {}; - for (const [key, entry] of input) { - entries[String(key)] = entry; + if (input instanceof URLSearchParams) { + return redactSearchParams(input, (entry) => + visitString(entry, depth + 1) + ).toString(); + } + if (input instanceof Error) { + return visitError(input, depth); + } + if (input instanceof Date) { + return input.toISOString(); + } + if (Array.isArray(input)) { + const output = input + .slice(0, resolved.maxArrayItems) + .map((entry) => visit(entry, depth + 1)); + if (input.length > resolved.maxArrayItems) { + output.push( + `[Truncated ${ + input.length - resolved.maxArrayItems + } items]` + ); + } + return output; + } + if (input instanceof Map) { + const entries: Record = {}; + for (const [key, entry] of input) { + entries[String(key)] = entry; + } + return visitObject(entries, depth); + } + if (input instanceof Set) { + return visit(Array.from(input), depth); } - return visitObject(entries, depth); - } - if (input instanceof Set) { - return visit(Array.from(input), depth); - } - return visitObject(input as Record, depth); + return visitObject(input as Record, depth); + } finally { + seen.delete(object); + } }; return visit(value, 0);