11/**
2- * Recursively redacts API keys in an object
3- * @param obj The object to redact API keys from
4- * @returns A new object with API keys redacted
2+ * Centralized redaction utilities for sensitive data
53 */
6- export const redactApiKeys = ( obj : any ) : any => {
7- if ( ! obj || typeof obj !== 'object' ) {
4+
5+ /** Standard marker used for all redacted values */
6+ export const REDACTED_MARKER = '[REDACTED]'
7+
8+ /**
9+ * Patterns for sensitive key names (case-insensitive matching)
10+ * These patterns match common naming conventions for sensitive data
11+ */
12+ const SENSITIVE_KEY_PATTERNS : RegExp [ ] = [
13+ / ^ a p i [ _ - ] ? k e y $ / i,
14+ / ^ a c c e s s [ _ - ] ? t o k e n $ / i,
15+ / ^ r e f r e s h [ _ - ] ? t o k e n $ / i,
16+ / ^ c l i e n t [ _ - ] ? s e c r e t $ / i,
17+ / ^ p r i v a t e [ _ - ] ? k e y $ / i,
18+ / ^ a u t h [ _ - ] ? t o k e n $ / i,
19+ / \b s e c r e t \b / i,
20+ / \b p a s s w o r d \b / i,
21+ / \b t o k e n \b / i,
22+ / \b c r e d e n t i a l \b / i,
23+ / \b a u t h o r i z a t i o n \b / i,
24+ / \b b e a r e r \b / i,
25+ / \b p r i v a t e \b / i,
26+ / \b a u t h \b / i,
27+ ]
28+
29+ /**
30+ * Patterns for sensitive values in strings (for redacting values, not keys)
31+ * Each pattern has a replacement function
32+ */
33+ const SENSITIVE_VALUE_PATTERNS : Array < {
34+ pattern : RegExp
35+ replacement : string
36+ } > = [
37+ // Bearer tokens
38+ {
39+ pattern : / B e a r e r \s + [ A - Z a - z 0 - 9 \- . _ ~ + / ] + = * / gi,
40+ replacement : `Bearer ${ REDACTED_MARKER } ` ,
41+ } ,
42+ // Basic auth
43+ {
44+ pattern : / B a s i c \s + [ A - Z a - z 0 - 9 + / ] + = * / gi,
45+ replacement : `Basic ${ REDACTED_MARKER } ` ,
46+ } ,
47+ // API keys that look like sk-..., pk-..., etc.
48+ {
49+ pattern : / \b ( s k | p k | a p i | k e y ) [ _ - ] [ A - Z a - z 0 - 9 \- . _ ] { 20 , } \b / gi,
50+ replacement : REDACTED_MARKER ,
51+ } ,
52+ // JSON-style password fields: password: "value" or password: 'value'
53+ {
54+ pattern : / p a s s w o r d [ ' " : \s ] * [ ' " ] [ ^ ' " ] + [ ' " ] / gi,
55+ replacement : `password: "${ REDACTED_MARKER } "` ,
56+ } ,
57+ // JSON-style token fields: token: "value" or token: 'value'
58+ {
59+ pattern : / t o k e n [ ' " : \s ] * [ ' " ] [ ^ ' " ] + [ ' " ] / gi,
60+ replacement : `token: "${ REDACTED_MARKER } "` ,
61+ } ,
62+ // JSON-style api_key fields: api_key: "value" or api-key: "value"
63+ {
64+ pattern : / a p i [ _ - ] ? k e y [ ' " : \s ] * [ ' " ] [ ^ ' " ] + [ ' " ] / gi,
65+ replacement : `api_key: "${ REDACTED_MARKER } "` ,
66+ } ,
67+ ]
68+
69+ /**
70+ * Checks if a key name matches any sensitive pattern
71+ * @param key - The key name to check
72+ * @returns True if the key is considered sensitive
73+ */
74+ export function isSensitiveKey ( key : string ) : boolean {
75+ const lowerKey = key . toLowerCase ( )
76+ return SENSITIVE_KEY_PATTERNS . some ( ( pattern ) => pattern . test ( lowerKey ) )
77+ }
78+
79+ /**
80+ * Redacts sensitive patterns from a string value
81+ * @param value - The string to redact
82+ * @returns The string with sensitive patterns redacted
83+ */
84+ export function redactSensitiveValues ( value : string ) : string {
85+ if ( ! value || typeof value !== 'string' ) {
86+ return value
87+ }
88+
89+ let result = value
90+ for ( const { pattern, replacement } of SENSITIVE_VALUE_PATTERNS ) {
91+ result = result . replace ( pattern , replacement )
92+ }
93+ return result
94+ }
95+
96+ /**
97+ * Recursively redacts sensitive data (API keys, passwords, tokens, etc.) from an object
98+ *
99+ * @param obj - The object to redact sensitive data from
100+ * @returns A new object with sensitive data redacted
101+ */
102+ export function redactApiKeys ( obj : any ) : any {
103+ if ( obj === null || obj === undefined ) {
104+ return obj
105+ }
106+
107+ if ( typeof obj !== 'object' ) {
8108 return obj
9109 }
10110
11111 if ( Array . isArray ( obj ) ) {
12- return obj . map ( redactApiKeys )
112+ return obj . map ( ( item ) => redactApiKeys ( item ) )
13113 }
14114
15115 const result : Record < string , any > = { }
16116
17117 for ( const [ key , value ] of Object . entries ( obj ) ) {
18- if (
19- key . toLowerCase ( ) === 'apikey' ||
20- key . toLowerCase ( ) === 'api_key' ||
21- key . toLowerCase ( ) === 'access_token' ||
22- / \b s e c r e t \b / i. test ( key . toLowerCase ( ) ) ||
23- / \b p a s s w o r d \b / i. test ( key . toLowerCase ( ) )
24- ) {
25- result [ key ] = '***REDACTED***'
118+ if ( isSensitiveKey ( key ) ) {
119+ result [ key ] = REDACTED_MARKER
26120 } else if ( typeof value === 'object' && value !== null ) {
27121 result [ key ] = redactApiKeys ( value )
28122 } else {
@@ -32,3 +126,67 @@ export const redactApiKeys = (obj: any): any => {
32126
33127 return result
34128}
129+
130+ /**
131+ * Sanitizes a string for safe logging by truncating and redacting sensitive patterns
132+ *
133+ * @param value - The string to sanitize
134+ * @param maxLength - Maximum length of the output (default: 100)
135+ * @returns The sanitized string
136+ */
137+ export function sanitizeForLogging ( value : string , maxLength = 100 ) : string {
138+ if ( ! value ) return ''
139+
140+ let sanitized = value . substring ( 0 , maxLength )
141+
142+ sanitized = redactSensitiveValues ( sanitized )
143+
144+ return sanitized
145+ }
146+
147+ /**
148+ * Sanitizes event data for error reporting/analytics
149+ *
150+ * @param event - The event data to sanitize
151+ * @returns Sanitized event data safe for external reporting
152+ */
153+ export function sanitizeEventData ( event : any ) : any {
154+ if ( event === null || event === undefined ) {
155+ return event
156+ }
157+
158+ if ( typeof event === 'string' ) {
159+ if ( isSensitiveKey ( event ) ) {
160+ return REDACTED_MARKER
161+ }
162+ return redactSensitiveValues ( event )
163+ }
164+
165+ if ( typeof event !== 'object' ) {
166+ return event
167+ }
168+
169+ if ( Array . isArray ( event ) ) {
170+ return event . map ( ( item ) => sanitizeEventData ( item ) )
171+ }
172+
173+ const sanitized : Record < string , unknown > = { }
174+
175+ for ( const [ key , value ] of Object . entries ( event ) ) {
176+ if ( isSensitiveKey ( key ) ) {
177+ continue
178+ }
179+
180+ if ( typeof value === 'string' ) {
181+ sanitized [ key ] = isSensitiveKey ( value ) ? REDACTED_MARKER : redactSensitiveValues ( value )
182+ } else if ( Array . isArray ( value ) ) {
183+ sanitized [ key ] = value . map ( ( v ) => sanitizeEventData ( v ) )
184+ } else if ( value && typeof value === 'object' ) {
185+ sanitized [ key ] = sanitizeEventData ( value )
186+ } else {
187+ sanitized [ key ] = value
188+ }
189+ }
190+
191+ return sanitized
192+ }
0 commit comments