@@ -15,7 +15,7 @@ const SENSITIVE_KEYS = new Set([
1515 '_token' ,
1616] ) ;
1717
18- const BEARER_V2_PATTERN = / ^ v 2 x [ a - f 0 - 9 ] { 32 , } $ / i ;
18+ const SENSITIVE_PREFIXES = [ 'v2x' , 'xprv' ] ;
1919
2020/**
2121 * Checks if a key is sensitive (case-insensitive)
@@ -25,27 +25,54 @@ function isSensitiveKey(key: string): boolean {
2525}
2626
2727/**
28- * Checks if a value matches the bearer v2 token pattern
28+ * Checks if a string value is sensitive based on known prefixes.
29+ * Unlike isSensitiveKey (which checks property names), this identifies
30+ * sensitive data by recognizable content patterns — useful when there
31+ * is no key context (e.g. top-level strings, array elements).
2932 */
30- function isBearerV2Token ( value : unknown ) : boolean {
31- return typeof value === 'string' && BEARER_V2_PATTERN . test ( value ) ;
33+ function isSensitiveStringValue ( s : string ) : boolean {
34+ return SENSITIVE_PREFIXES . some ( ( prefix ) => s . startsWith ( prefix ) ) ;
35+ }
36+
37+ export function getErrorData ( error : unknown ) : unknown {
38+ if ( ! ( error && error instanceof Error ) ) {
39+ return error ;
40+ }
41+
42+ const errorData : Record < string , unknown > = {
43+ name : error . name ,
44+ } ;
45+
46+ for ( const key of Object . getOwnPropertyNames ( error ) ) {
47+ const value = ( error as unknown as Record < string , unknown > ) [ key ] ;
48+ errorData [ key ] = value instanceof Error ? getErrorData ( value ) : value ;
49+ }
50+
51+ return errorData ;
3252}
3353
3454/**
3555 * Recursively sanitizes an object, replacing sensitive values with '<REMOVED>'
3656 * Handles circular references and nested structures
3757 */
3858export function sanitize ( obj : unknown , seen = new WeakSet < Record < string , unknown > > ( ) , depth = 0 ) : unknown {
39- // Prevent infinite recursion
40- if ( depth > 50 ) {
59+ if ( depth > 25 ) {
4160 return '[Max Depth Exceeded]' ;
4261 }
4362
44- // Handle primitives
4563 if ( obj === null || obj === undefined ) {
4664 return obj ;
4765 }
4866
67+ // Handle BigInt (JSON.stringify(1n) throws TypeError)
68+ if ( typeof obj === 'bigint' ) {
69+ return obj . toString ( ) ;
70+ }
71+
72+ if ( typeof obj === 'string' ) {
73+ return isSensitiveStringValue ( obj ) ? '<REMOVED>' : obj ;
74+ }
75+
4976 if ( typeof obj !== 'object' ) {
5077 return obj ;
5178 }
@@ -62,16 +89,21 @@ export function sanitize(obj: unknown, seen = new WeakSet<Record<string, unknown
6289 return obj . map ( ( item ) => sanitize ( item , seen , depth + 1 ) ) ;
6390 }
6491
92+ // Handle Date objects
93+ if ( obj instanceof Date ) {
94+ return isNaN ( obj . getTime ( ) ) ? '[Invalid Date]' : obj . toISOString ( ) ;
95+ }
96+
6597 // Handle objects
6698 const sanitized : Record < string , unknown > = { } ;
6799
68100 for ( const [ key , value ] of Object . entries ( obj ) ) {
69- if ( isSensitiveKey ( key ) || isBearerV2Token ( value ) ) {
101+ if ( isSensitiveKey ( key ) || ( typeof value === 'string' && isSensitiveStringValue ( value ) ) ) {
70102 sanitized [ key ] = '<REMOVED>' ;
71- } else if ( typeof value === 'object' && value !== null ) {
72- sanitized [ key ] = sanitize ( value , seen , depth + 1 ) ;
103+ } else if ( value instanceof Error ) {
104+ sanitized [ key ] = sanitize ( getErrorData ( value ) , seen , depth + 1 ) ;
73105 } else {
74- sanitized [ key ] = value ;
106+ sanitized [ key ] = sanitize ( value , seen , depth + 1 ) ;
75107 }
76108 }
77109
0 commit comments