@devilsdev/rag-pipeline-utils 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,338 @@
1
+ /**
2
+ * @fileoverview Secure Logging Utility
3
+ * Redacts sensitive values from log messages and objects to prevent secret leakage
4
+ *
5
+ * @author DevilsDev Team
6
+ * @since 2.2.1
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * Sensitive field names that should be redacted
13
+ */
14
+ const SENSITIVE_FIELD_NAMES = new Set([
15
+ 'password',
16
+ 'passwd',
17
+ 'pwd',
18
+ 'secret',
19
+ 'api_key',
20
+ 'apiKey',
21
+ 'apikey',
22
+ 'api-key',
23
+ 'token',
24
+ 'accessToken',
25
+ 'access_token',
26
+ 'refreshToken',
27
+ 'refresh_token',
28
+ 'auth',
29
+ 'authorization',
30
+ 'authtoken',
31
+ 'sessionid',
32
+ 'session_id',
33
+ 'sessionId',
34
+ 'jwt',
35
+ 'bearer',
36
+ 'private_key',
37
+ 'privatekey', // Added lowercase variant
38
+ 'privateKey',
39
+ 'private-key',
40
+ 'client_secret',
41
+ 'clientSecret',
42
+ 'client-secret',
43
+ 'encryption_key',
44
+ 'encryptionKey',
45
+ 'signing_key',
46
+ 'signingKey',
47
+ 'oauth_token',
48
+ 'oauthToken',
49
+ 'credentials',
50
+ 'credential',
51
+ 'ssn',
52
+ 'credit_card',
53
+ 'creditCard',
54
+ 'cvv',
55
+ 'pin',
56
+ 'salt',
57
+ ]);
58
+
59
+ /**
60
+ * Patterns that match secret-like values
61
+ */
62
+ const SECRET_PATTERNS = [
63
+ // AWS credentials
64
+ /AKIA[0-9A-Z]{16}/gi, // AWS Access Key ID
65
+ /aws_secret_access_key\s*=\s*[A-Za-z0-9/+=]{40}/gi,
66
+
67
+ // Generic API keys (32+ hex chars, 20+ base64 chars)
68
+ /[a-f0-9]{32,}/gi, // Hex keys
69
+ /[A-Za-z0-9+/]{20,}={0,2}/g, // Base64 keys
70
+
71
+ // JWT tokens (3 base64 parts separated by dots)
72
+ /eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
73
+
74
+ // Bearer tokens
75
+ /Bearer\s+[A-Za-z0-9_\-\.]+/gi,
76
+
77
+ // GitHub tokens
78
+ /gh[pousr]_[A-Za-z0-9]{36}/g,
79
+
80
+ // Generic passwords in URL format
81
+ /:\/\/[^:]+:([^@]+)@/g, // Captures password in user:password@host
82
+
83
+ // Credit card numbers (basic pattern)
84
+ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
85
+
86
+ // SSN pattern
87
+ /\b\d{3}-\d{2}-\d{4}\b/g,
88
+ ];
89
+
90
+ /**
91
+ * Redaction marker
92
+ */
93
+ const REDACTED = '[REDACTED]';
94
+
95
+ /**
96
+ * Check if a field name is sensitive
97
+ * @param {string} fieldName - Field name to check
98
+ * @returns {boolean} True if sensitive
99
+ */
100
+ function isSensitiveFieldName(fieldName) {
101
+ if (typeof fieldName !== 'string') return false;
102
+
103
+ const lowerFieldName = fieldName.toLowerCase();
104
+ return SENSITIVE_FIELD_NAMES.has(lowerFieldName);
105
+ }
106
+
107
+ /**
108
+ * Check if a value matches a secret pattern
109
+ * @param {string} value - Value to check
110
+ * @returns {boolean} True if matches secret pattern
111
+ */
112
+ function matchesSecretPattern(value) {
113
+ if (typeof value !== 'string') return false;
114
+
115
+ // Skip short values to avoid false positives
116
+ if (value.length < 8) return false;
117
+
118
+ // Check against secret patterns
119
+ for (const pattern of SECRET_PATTERNS) {
120
+ // Reset regex lastIndex to avoid stateful regex issues
121
+ pattern.lastIndex = 0;
122
+
123
+ if (pattern.test(value)) {
124
+ return true;
125
+ }
126
+ }
127
+
128
+ return false;
129
+ }
130
+
131
+ /**
132
+ * Redact a string value if it contains secrets
133
+ * @param {string} value - String value to redact
134
+ * @returns {string} Redacted value if sensitive, original otherwise
135
+ */
136
+ function redactStringValue(value) {
137
+ if (typeof value !== 'string') return value;
138
+
139
+ // Short strings are unlikely to be secrets
140
+ if (value.length < 8) return value;
141
+
142
+ // Check if entire value matches secret pattern
143
+ if (matchesSecretPattern(value)) {
144
+ return REDACTED;
145
+ }
146
+
147
+ // Redact parts of the string that match patterns
148
+ let redacted = value;
149
+ for (const pattern of SECRET_PATTERNS) {
150
+ pattern.lastIndex = 0;
151
+ redacted = redacted.replace(pattern, REDACTED);
152
+ }
153
+
154
+ return redacted;
155
+ }
156
+
157
+ /**
158
+ * Redact sensitive fields in an object
159
+ * @param {any} obj - Object to redact
160
+ * @param {number} depth - Current recursion depth
161
+ * @param {number} maxDepth - Maximum recursion depth
162
+ * @returns {any} Redacted object
163
+ */
164
+ function redactObject(obj, depth = 0, maxDepth = 10) {
165
+ // Prevent infinite recursion
166
+ if (depth > maxDepth) return '[MAX_DEPTH_EXCEEDED]';
167
+
168
+ // Handle primitives
169
+ if (obj === null || obj === undefined) return obj;
170
+ if (typeof obj === 'string') return redactStringValue(obj);
171
+ if (typeof obj === 'number' || typeof obj === 'boolean') return obj;
172
+
173
+ // Handle Date objects
174
+ if (obj instanceof Date) return obj;
175
+
176
+ // Handle Error objects specially to preserve standard properties
177
+ if (obj instanceof Error) {
178
+ const errorObj = {
179
+ name: obj.name,
180
+ message: obj.message,
181
+ stack: obj.stack,
182
+ };
183
+
184
+ // Include any other enumerable properties (like code)
185
+ for (const [key, value] of Object.entries(obj)) {
186
+ if (!['name', 'message', 'stack'].includes(key)) {
187
+ errorObj[key] = redactObject(value, depth + 1, maxDepth);
188
+ }
189
+ }
190
+
191
+ return errorObj;
192
+ }
193
+
194
+ // Handle arrays
195
+ if (Array.isArray(obj)) {
196
+ return obj.map((item) => redactObject(item, depth + 1, maxDepth));
197
+ }
198
+
199
+ // Handle objects
200
+ if (typeof obj === 'object') {
201
+ const redacted = {};
202
+
203
+ for (const [key, value] of Object.entries(obj)) {
204
+ // For sensitive field names, check the value type
205
+ if (isSensitiveFieldName(key)) {
206
+ // If value is an object, still traverse it to redact nested sensitive fields
207
+ if (
208
+ typeof value === 'object' &&
209
+ value !== null &&
210
+ !Array.isArray(value)
211
+ ) {
212
+ redacted[key] = redactObject(value, depth + 1, maxDepth);
213
+ } else {
214
+ // For primitives and arrays, redact the entire value
215
+ redacted[key] = REDACTED;
216
+ }
217
+ } else if (typeof value === 'string') {
218
+ // Redact string values that match patterns
219
+ redacted[key] = redactStringValue(value);
220
+ } else if (typeof value === 'object' && value !== null) {
221
+ // Recursively redact nested objects
222
+ redacted[key] = redactObject(value, depth + 1, maxDepth);
223
+ } else {
224
+ // Keep primitive values as-is
225
+ redacted[key] = value;
226
+ }
227
+ }
228
+
229
+ return redacted;
230
+ }
231
+
232
+ // Default fallback
233
+ return obj;
234
+ }
235
+
236
+ /**
237
+ * Redact sensitive information from log data
238
+ * @param {string} message - Log message
239
+ * @param {object} meta - Log metadata object
240
+ * @returns {object} Redacted message and metadata
241
+ */
242
+ function redactLogData(message, meta = {}) {
243
+ const redactedMessage =
244
+ typeof message === 'string' ? redactStringValue(message) : message;
245
+
246
+ const redactedMeta = redactObject(meta);
247
+
248
+ return {
249
+ message: redactedMessage,
250
+ meta: redactedMeta,
251
+ };
252
+ }
253
+
254
+ /**
255
+ * Create a redacting wrapper for any logger
256
+ * @param {object} logger - Logger instance to wrap
257
+ * @returns {object} Wrapped logger with automatic redaction
258
+ */
259
+ function createSecureLogger(logger) {
260
+ return {
261
+ error(message, meta = {}) {
262
+ const { message: redactedMsg, meta: redactedMeta } = redactLogData(
263
+ message,
264
+ meta,
265
+ );
266
+ return logger.error(redactedMsg, redactedMeta);
267
+ },
268
+
269
+ warn(message, meta = {}) {
270
+ const { message: redactedMsg, meta: redactedMeta } = redactLogData(
271
+ message,
272
+ meta,
273
+ );
274
+ return logger.warn(redactedMsg, redactedMeta);
275
+ },
276
+
277
+ info(message, meta = {}) {
278
+ const { message: redactedMsg, meta: redactedMeta } = redactLogData(
279
+ message,
280
+ meta,
281
+ );
282
+ return logger.info(redactedMsg, redactedMeta);
283
+ },
284
+
285
+ debug(message, meta = {}) {
286
+ const { message: redactedMsg, meta: redactedMeta } = redactLogData(
287
+ message,
288
+ meta,
289
+ );
290
+ return logger.debug(redactedMsg, redactedMeta);
291
+ },
292
+
293
+ trace(message, meta = {}) {
294
+ const { message: redactedMsg, meta: redactedMeta } = redactLogData(
295
+ message,
296
+ meta,
297
+ );
298
+ return logger.trace(redactedMsg, redactedMeta);
299
+ },
300
+
301
+ // Preserve other logger methods
302
+ child(...args) {
303
+ const childLogger = logger.child(...args);
304
+ return createSecureLogger(childLogger);
305
+ },
306
+
307
+ withCorrelation(...args) {
308
+ return logger.withCorrelation(...args);
309
+ },
310
+
311
+ getContext(...args) {
312
+ return logger.getContext(...args);
313
+ },
314
+
315
+ getMetrics(...args) {
316
+ return logger.getMetrics(...args);
317
+ },
318
+
319
+ resetMetrics(...args) {
320
+ return logger.resetMetrics?.(...args);
321
+ },
322
+ };
323
+ }
324
+
325
+ module.exports = {
326
+ redactObject,
327
+ redactStringValue,
328
+ redactLogData,
329
+ createSecureLogger,
330
+ isSensitiveFieldName,
331
+ matchesSecretPattern,
332
+ SENSITIVE_FIELD_NAMES,
333
+ SECRET_PATTERNS,
334
+ REDACTED,
335
+ };
336
+
337
+ // CommonJS/ESM interop
338
+ module.exports.default = module.exports;
@@ -8,6 +8,7 @@
8
8
 
9
9
  const { AsyncLocalStorage } = require('async_hooks');
10
10
  const crypto = require('crypto');
11
+ const { redactLogData } = require('./secure-logging');
11
12
 
12
13
  /**
13
14
  * Async local storage for correlation context
@@ -36,6 +37,7 @@ class StructuredLogger {
36
37
  this.logLevel = options.logLevel || process.env.LOG_LEVEL || 'info';
37
38
  this.enableCorrelation = options.enableCorrelation !== false;
38
39
  this.enableMetrics = options.enableMetrics !== false;
40
+ this.enableSecureLogging = options.enableSecureLogging !== false; // Default enabled for security
39
41
  this.outputFormat = options.outputFormat || 'json'; // json, console
40
42
 
41
43
  // Performance tracking
@@ -201,7 +203,12 @@ class StructuredLogger {
201
203
  error(message, meta = {}) {
202
204
  if (!this.shouldLog('error')) return;
203
205
 
204
- const entry = this.createLogEntry('error', message, meta);
206
+ // Redact sensitive information if secure logging is enabled
207
+ const { message: safeMessage, meta: safeMeta } = this.enableSecureLogging
208
+ ? redactLogData(message, meta)
209
+ : { message, meta };
210
+
211
+ const entry = this.createLogEntry('error', safeMessage, safeMeta);
205
212
  this.writeLog(entry);
206
213
  }
207
214
 
@@ -213,7 +220,12 @@ class StructuredLogger {
213
220
  warn(message, meta = {}) {
214
221
  if (!this.shouldLog('warn')) return;
215
222
 
216
- const entry = this.createLogEntry('warn', message, meta);
223
+ // Redact sensitive information if secure logging is enabled
224
+ const { message: safeMessage, meta: safeMeta } = this.enableSecureLogging
225
+ ? redactLogData(message, meta)
226
+ : { message, meta };
227
+
228
+ const entry = this.createLogEntry('warn', safeMessage, safeMeta);
217
229
  this.writeLog(entry);
218
230
  }
219
231
 
@@ -225,7 +237,12 @@ class StructuredLogger {
225
237
  info(message, meta = {}) {
226
238
  if (!this.shouldLog('info')) return;
227
239
 
228
- const entry = this.createLogEntry('info', message, meta);
240
+ // Redact sensitive information if secure logging is enabled
241
+ const { message: safeMessage, meta: safeMeta } = this.enableSecureLogging
242
+ ? redactLogData(message, meta)
243
+ : { message, meta };
244
+
245
+ const entry = this.createLogEntry('info', safeMessage, safeMeta);
229
246
  this.writeLog(entry);
230
247
  }
231
248
 
@@ -237,7 +254,12 @@ class StructuredLogger {
237
254
  debug(message, meta = {}) {
238
255
  if (!this.shouldLog('debug')) return;
239
256
 
240
- const entry = this.createLogEntry('debug', message, meta);
257
+ // Redact sensitive information if secure logging is enabled
258
+ const { message: safeMessage, meta: safeMeta } = this.enableSecureLogging
259
+ ? redactLogData(message, meta)
260
+ : { message, meta };
261
+
262
+ const entry = this.createLogEntry('debug', safeMessage, safeMeta);
241
263
  this.writeLog(entry);
242
264
  }
243
265
 
@@ -249,7 +271,12 @@ class StructuredLogger {
249
271
  trace(message, meta = {}) {
250
272
  if (!this.shouldLog('trace')) return;
251
273
 
252
- const entry = this.createLogEntry('trace', message, meta);
274
+ // Redact sensitive information if secure logging is enabled
275
+ const { message: safeMessage, meta: safeMeta } = this.enableSecureLogging
276
+ ? redactLogData(message, meta)
277
+ : { message, meta };
278
+
279
+ const entry = this.createLogEntry('trace', safeMessage, safeMeta);
253
280
  this.writeLog(entry);
254
281
  }
255
282