diff --git a/modules/logger/src/sanitizeLog.ts b/modules/logger/src/sanitizeLog.ts index 96822e4dab..4286e7c363 100644 --- a/modules/logger/src/sanitizeLog.ts +++ b/modules/logger/src/sanitizeLog.ts @@ -15,7 +15,7 @@ const SENSITIVE_KEYS = new Set([ '_token', ]); -const BEARER_V2_PATTERN = /^v2x[a-f0-9]{32,}$/i; +const BEARER_V2_PATTERN = /^v2x[a-fA-F0-9]{32,}$/; /** * Checks if a key is sensitive (case-insensitive) @@ -50,7 +50,11 @@ export function getErrorData(error: unknown): unknown { /** * Recursively sanitizes an object, replacing sensitive values with '' - * Handles circular references and nested structures + * Handles circular references, nested structures, and various JavaScript types + * @param obj - The value to sanitize + * @param seen - WeakSet to track circular references + * @param depth - Current recursion depth + * @returns Sanitized value */ export function sanitize(obj: unknown, seen = new WeakSet>(), depth = 0): unknown { if (depth > 25) { @@ -74,24 +78,63 @@ export function sanitize(obj: unknown, seen = new WeakSet)) { + // Handle Date objects before circular check (Dates should be converted, not tracked) + if (obj instanceof Date) { + return obj.toISOString(); + } + + // Handle circular references (only for objects that can be in WeakSet) + const objAsRecord = obj as Record; + if (seen.has(objAsRecord)) { return '[Circular]'; } - seen.add(obj as Record); + seen.add(objAsRecord); // Handle arrays if (Array.isArray(obj)) { return obj.map((item) => sanitize(item, seen, depth + 1)); } - // Handle Date objects - if (obj instanceof Date) { - return obj.toISOString(); + // Handle RegExp + if (obj instanceof RegExp) { + return obj.toString(); + } + + // Handle Buffer (Node.js) + if (typeof Buffer !== 'undefined' && Buffer.isBuffer(obj)) { + return ''; + } + + // Handle typed arrays + if (ArrayBuffer.isView(obj) && !(obj instanceof DataView)) { + return ''; + } + + // Handle Map + if (obj instanceof Map) { + const sanitized = new Map(); + for (const [key, value] of obj.entries()) { + const keyStr = typeof key === 'string' ? key : String(key); + if (isSensitiveKey(keyStr) || isBearerV2Token(value)) { + sanitized.set(key, ''); + } else { + sanitized.set(key, sanitize(value, seen, depth + 1)); + } + } + return sanitized; + } + + // Handle Set + if (obj instanceof Set) { + const sanitized = new Set(); + for (const value of obj.values()) { + sanitized.add(sanitize(value, seen, depth + 1)); + } + return sanitized; } - // Handle objects + // Handle plain objects and other object types const sanitized: Record = {}; for (const [key, value] of Object.entries(obj)) { diff --git a/modules/logger/test/unit/sanitizeLog.ts b/modules/logger/test/unit/sanitizeLog.ts index 9af75378b2..d3322d32ce 100644 --- a/modules/logger/test/unit/sanitizeLog.ts +++ b/modules/logger/test/unit/sanitizeLog.ts @@ -246,6 +246,103 @@ describe('sanitize', function () { }); }); + describe('RegExp handling', function () { + it('should convert RegExp to string', function () { + assert.strictEqual(sanitize(/test/gi), '/test/gi'); + }); + + it('should handle RegExp in objects', function () { + const obj = { pattern: /[a-z]+/i, name: 'validator' }; + const result = sanitize(obj) as any; + assert.strictEqual(result.pattern, '/[a-z]+/i'); + assert.strictEqual(result.name, 'validator'); + }); + }); + + describe('Buffer handling', function () { + it('should handle Buffer objects', function () { + if (typeof Buffer !== 'undefined') { + const buf = Buffer.from('test data'); + assert.strictEqual(sanitize(buf), ''); + } + }); + + it('should handle Buffer in objects', function () { + if (typeof Buffer !== 'undefined') { + const obj = { data: Buffer.from('secret'), label: 'file' }; + const result = sanitize(obj) as any; + assert.strictEqual(result.data, ''); + assert.strictEqual(result.label, 'file'); + } + }); + }); + + describe('TypedArray handling', function () { + it('should handle Uint8Array', function () { + const arr = new Uint8Array([1, 2, 3]); + assert.strictEqual(sanitize(arr), ''); + }); + + it('should handle Int32Array in objects', function () { + const obj = { numbers: new Int32Array([10, 20, 30]), count: 3 }; + const result = sanitize(obj) as any; + assert.strictEqual(result.numbers, ''); + assert.strictEqual(result.count, 3); + }); + }); + + describe('Map handling', function () { + it('should sanitize Map entries', function () { + const map = new Map([ + ['user', 'alice'], + ['password', 'secret'], + ]); + const result = sanitize(map) as Map; + assert.ok(result instanceof Map); + assert.strictEqual(result.get('user'), 'alice'); + assert.strictEqual(result.get('password'), ''); + }); + + it('should handle nested Map in objects', function () { + const obj = { + config: new Map([ + ['host', 'localhost'], + ['token', 'secret'], + ]), + }; + const result = sanitize(obj) as any; + assert.ok(result.config instanceof Map); + assert.strictEqual(result.config.get('host'), 'localhost'); + assert.strictEqual(result.config.get('token'), ''); + }); + + it('should handle v2 token in Map values', function () { + const map = new Map([['auth', V2_TOKEN]]); + const result = sanitize(map) as Map; + assert.strictEqual(result.get('auth'), ''); + }); + }); + + describe('Set handling', function () { + it('should sanitize Set entries', function () { + const set = new Set(['user1', V2_TOKEN, 'user3']); + const result = sanitize(set) as Set; + assert.ok(result instanceof Set); + assert.strictEqual(result.has('user1'), true); + assert.strictEqual(result.has(''), true); + assert.strictEqual(result.has('user3'), true); + assert.strictEqual(result.has(V2_TOKEN), false); + }); + + it('should handle nested Set in objects', function () { + const obj = { tags: new Set(['public', 'private']) }; + const result = sanitize(obj) as any; + assert.ok(result.tags instanceof Set); + assert.strictEqual(result.tags.has('public'), true); + assert.strictEqual(result.tags.has('private'), true); + }); + }); + describe('mixed complex object', function () { it('should handle all data types together', function () { const complexObj = { @@ -270,6 +367,22 @@ describe('sanitize', function () { assert.strictEqual(result.tags[0], 'transfer'); assert.strictEqual(result.tags[1], 'urgent'); }); + + it('should handle all new data types together', function () { + const complexObj = { + pattern: /test/gi, + bigNum: 999n, + tags: new Set(['a', 'b']), + config: new Map([['key', 'value']]), + date: new Date('2026-01-01T00:00:00.000Z'), + }; + const result = sanitize(complexObj) as any; + assert.strictEqual(result.pattern, '/test/gi'); + assert.strictEqual(result.bigNum, '999'); + assert.ok(result.tags instanceof Set); + assert.ok(result.config instanceof Map); + assert.strictEqual(result.date, '2026-01-01T00:00:00.000Z'); + }); }); });