diff --git a/src/libs/sanitizeLogParams.ts b/src/libs/sanitizeLogParams.ts index bf4e6ff9b17e..6949f6aa8330 100644 --- a/src/libs/sanitizeLogParams.ts +++ b/src/libs/sanitizeLogParams.ts @@ -15,6 +15,8 @@ const SENSITIVE_KEYS = new Set([ 'oldDotAutoGeneratedPassword', 'apiKey', 'clientSecret', + 'cardNumber', + 'cardCVV', ]); const REDACTED = ''; diff --git a/tests/unit/sanitizeLogParamsTest.ts b/tests/unit/sanitizeLogParamsTest.ts new file mode 100644 index 000000000000..fc6cbe8a23a7 --- /dev/null +++ b/tests/unit/sanitizeLogParamsTest.ts @@ -0,0 +1,70 @@ +import sanitizeLogParams, {sanitizeUrlForLogging} from '@libs/sanitizeLogParams'; + +describe('sanitizeLogParams', () => { + it('redacts known sensitive keys', () => { + const result = sanitizeLogParams({authToken: 'secret', password: 'hunter2', token: 'abc'}); + expect(result).toEqual({authToken: '', password: '', token: ''}); + }); + + it('redacts card number and CVV so payment card details never reach the logs', () => { + const result = sanitizeLogParams({ + command: 'AddPaymentCard', + data: { + cardNumber: '4111111111111111', + cardCVV: '123', + cardMonth: '04', + cardYear: '2030', + addressZip: '94105', + }, + }); + + expect(result).toEqual({ + command: 'AddPaymentCard', + data: { + cardNumber: '', + cardCVV: '', + cardMonth: '04', + cardYear: '2030', + addressZip: '94105', + }, + }); + }); + + it('leaves non-sensitive keys untouched', () => { + const result = sanitizeLogParams({policyID: '1', currency: 'USD'}); + expect(result).toEqual({policyID: '1', currency: 'USD'}); + }); + + it('redacts sensitive keys nested inside arrays', () => { + const result = sanitizeLogParams({cards: [{cardNumber: '4111111111111111'}, {cardNumber: '5500000000000004'}]}); + expect(result).toEqual({cards: [{cardNumber: ''}, {cardNumber: ''}]}); + }); + + it('stops recursing past the max depth', () => { + const deep = {a: {b: {c: {d: {e: {f: {cardNumber: '4111111111111111'}}}}}}}; + const result = sanitizeLogParams(deep); + + // Beyond depth 5 the sanitizer returns the object as-is, so the deeply-nested value is not redacted. + expect(result.a.b.c.d.e.f.cardNumber).toBe('4111111111111111'); + }); + + it('returns primitives unchanged', () => { + expect(sanitizeLogParams('plain string')).toBe('plain string'); + expect(sanitizeLogParams(42)).toBe(42); + expect(sanitizeLogParams(null)).toBeNull(); + }); +}); + +describe('sanitizeUrlForLogging', () => { + it('redacts the validate code in /v/:accountID/:validateCode paths', () => { + expect(sanitizeUrlForLogging('/v/12345/abcdef')).toBe('/v/12345/'); + }); + + it('redacts the validate code in /u/:accountID/:validateCode paths', () => { + expect(sanitizeUrlForLogging('/u/12345/abcdef')).toBe('/u/12345/'); + }); + + it('strips the query string', () => { + expect(sanitizeUrlForLogging('https://example.com/path?authToken=secret')).toBe('https://example.com/path?'); + }); +});