@devilsdev/rag-pipeline-utils 2.2.1 → 2.3.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.
Files changed (40) hide show
  1. package/README.md +260 -19
  2. package/contracts/contract-schema.json +189 -0
  3. package/dist/index.cjs +19 -0
  4. package/dist/index.mjs +15 -1
  5. package/package.json +3 -2
  6. package/src/cli/commands/wizard.js +627 -0
  7. package/src/cli/input-sanitization-middleware.js +356 -0
  8. package/src/cli/templates/development.template.json +64 -0
  9. package/src/cli/templates/minimal.template.json +33 -0
  10. package/src/cli/templates/production.template.json +104 -0
  11. package/src/cli/templates/testing.template.json +45 -0
  12. package/src/config/connection-pool-config.json +259 -0
  13. package/src/config/memory-optimization.js +414 -0
  14. package/src/config/redaction-patterns.json +150 -0
  15. package/src/config/sanitization-rules.json +323 -0
  16. package/src/core/plugin-registry.js +266 -20
  17. package/src/dag/core/error-context.js +391 -0
  18. package/src/dag/core/execution-scheduler.js +350 -0
  19. package/src/dag/core/topology-validation.js +415 -0
  20. package/src/dag/dag-engine.js +26 -424
  21. package/src/dev/README.md +126 -0
  22. package/src/dev/dev-server.js +356 -0
  23. package/src/dev/examples/hot-reload-usage.js +355 -0
  24. package/src/dev/hot-reload.js +807 -0
  25. package/src/enterprise/sso-integration.js +67 -25
  26. package/src/index.js +19 -0
  27. package/src/observability/event-logger.js +62 -2
  28. package/src/plugins/optimized-openai-embedder.js +169 -0
  29. package/src/security/jwt-config.js +431 -0
  30. package/src/security/jwt-validator.js +701 -0
  31. package/src/utils/api-client-pool-integration.js +436 -0
  32. package/src/utils/batch-processor.js +710 -0
  33. package/src/utils/connection-pool.js +605 -0
  34. package/src/utils/contract-validator.js +460 -0
  35. package/src/utils/error-formatter.js +603 -0
  36. package/src/utils/examples/error-formatter-usage.js +351 -0
  37. package/src/utils/input-sanitizer.js +967 -0
  38. package/src/utils/memory-monitor.js +800 -0
  39. package/src/utils/secure-logger-config.js +352 -0
  40. package/src/utils/secure-logger.js +576 -0
@@ -0,0 +1,576 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Secure Logger with Automatic Secret Redaction
5
+ *
6
+ * Provides utilities for logging data while automatically redacting sensitive
7
+ * information like API keys, tokens, passwords, and other secrets.
8
+ *
9
+ * Features:
10
+ * - Pattern-based redaction for common secret types
11
+ * - Custom pattern support via regex
12
+ * - Deep object traversal while preserving structure
13
+ * - Redaction statistics for monitoring
14
+ * - Minimal performance impact (<5ms per log)
15
+ *
16
+ * @module secure-logger
17
+ * @since 2.2.5
18
+ */
19
+
20
+ /**
21
+ * Default redaction patterns for common secrets
22
+ * Each pattern has a regex and a replacement strategy
23
+ *
24
+ * @private
25
+ */
26
+ const DEFAULT_PATTERNS = {
27
+ // API Keys - various formats
28
+ apiKey: {
29
+ regex:
30
+ /\b([a-zA-Z0-9_-]*(?:api[_-]?key|apikey|key)[a-zA-Z0-9_-]*[:=]\s*)(['"]?)([a-zA-Z0-9_\-]{16,})(['"]?)/gi,
31
+ replacement: (match, prefix, quote1, key, quote2) =>
32
+ `${prefix}${quote1}[REDACTED_API_KEY]${quote2}`,
33
+ description: 'API keys in key-value format',
34
+ },
35
+
36
+ // Bearer tokens
37
+ bearerToken: {
38
+ regex: /\b(bearer\s+)([a-zA-Z0-9_\-\.=]+)/gi,
39
+ replacement: '$1[REDACTED_BEARER_TOKEN]',
40
+ description: 'Bearer authentication tokens',
41
+ },
42
+
43
+ // JWT tokens
44
+ jwt: {
45
+ regex: /\b(eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+)/g,
46
+ replacement: '[REDACTED_JWT]',
47
+ description: 'JSON Web Tokens',
48
+ },
49
+
50
+ // AWS Access Keys
51
+ awsAccessKey: {
52
+ regex: /\b(AKIA[0-9A-Z]{16})\b/g,
53
+ replacement: '[REDACTED_AWS_KEY]',
54
+ description: 'AWS access keys',
55
+ },
56
+
57
+ // AWS Secret Keys
58
+ awsSecretKey: {
59
+ regex:
60
+ /\b(aws[_-]?secret[_-]?access[_-]?key[:=]\s*)(['"]?)([a-zA-Z0-9/+=]{40})(['"]?)/gi,
61
+ replacement: (match, prefix, quote1, key, quote2) =>
62
+ `${prefix}${quote1}[REDACTED_AWS_SECRET]${quote2}`,
63
+ description: 'AWS secret access keys',
64
+ },
65
+
66
+ // Generic passwords
67
+ password: {
68
+ regex:
69
+ /\b([a-zA-Z0-9_-]*password[a-zA-Z0-9_-]*[:=]\s*)(['"]?)([^\s'"]{6,})(['"]?)/gi,
70
+ replacement: (match, prefix, quote1, pwd, quote2) =>
71
+ `${prefix}${quote1}[REDACTED_PASSWORD]${quote2}`,
72
+ description: 'Password fields',
73
+ },
74
+
75
+ // Generic tokens
76
+ token: {
77
+ regex:
78
+ /\b([a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*[:=]\s*)(['"]?)([a-zA-Z0-9_\-\.=]{16,})(['"]?)/gi,
79
+ replacement: (match, prefix, quote1, token, quote2) =>
80
+ `${prefix}${quote1}[REDACTED_TOKEN]${quote2}`,
81
+ description: 'Generic token fields',
82
+ },
83
+
84
+ // Generic secrets
85
+ secret: {
86
+ regex:
87
+ /\b([a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*[:=]\s*)(['"]?)([^\s'"]{6,})(['"]?)/gi,
88
+ replacement: (match, prefix, quote1, secret, quote2) =>
89
+ `${prefix}${quote1}[REDACTED_SECRET]${quote2}`,
90
+ description: 'Generic secret fields',
91
+ },
92
+
93
+ // Private keys (PEM format)
94
+ privateKey: {
95
+ regex:
96
+ /(-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----)([\s\S]*?)(-----END\s+(?:RSA\s+)?PRIVATE\s+KEY-----)/gi,
97
+ replacement: '$1\n[REDACTED_PRIVATE_KEY]\n$3',
98
+ description: 'PEM-format private keys',
99
+ },
100
+
101
+ // Credit card numbers (basic pattern)
102
+ creditCard: {
103
+ regex: /\b(\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4})\b/g,
104
+ replacement: '[REDACTED_CC]',
105
+ description: 'Credit card numbers',
106
+ },
107
+
108
+ // Email addresses (optional redaction)
109
+ email: {
110
+ regex: /\b([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})\b/g,
111
+ replacement: '[REDACTED_EMAIL]',
112
+ description: 'Email addresses',
113
+ enabled: false, // Disabled by default
114
+ },
115
+
116
+ // IP addresses (optional redaction)
117
+ ipAddress: {
118
+ regex: /\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\b/g,
119
+ replacement: '[REDACTED_IP]',
120
+ description: 'IP addresses',
121
+ enabled: false, // Disabled by default
122
+ },
123
+ };
124
+
125
+ /**
126
+ * Field names that commonly contain sensitive data
127
+ * These fields will be redacted regardless of content pattern
128
+ *
129
+ * @private
130
+ */
131
+ const SENSITIVE_FIELD_NAMES = new Set([
132
+ 'password',
133
+ 'passwd',
134
+ 'pwd',
135
+ 'secret',
136
+ 'api_key',
137
+ 'apikey',
138
+ 'apiKey',
139
+ 'access_token',
140
+ 'accessToken',
141
+ 'refresh_token',
142
+ 'refreshToken',
143
+ 'auth_token',
144
+ 'authToken',
145
+ 'bearer_token',
146
+ 'bearerToken',
147
+ 'private_key',
148
+ 'privateKey',
149
+ 'client_secret',
150
+ 'clientSecret',
151
+ 'jwt',
152
+ 'authorization',
153
+ 'auth',
154
+ 'credentials',
155
+ 'credential',
156
+ ]);
157
+
158
+ /**
159
+ * Secure Logger class
160
+ * Handles redaction of sensitive data from log entries
161
+ *
162
+ * @class SecureLogger
163
+ */
164
+ class SecureLogger {
165
+ /**
166
+ * Create a SecureLogger instance
167
+ *
168
+ * @param {Object} options - Configuration options
169
+ * @param {boolean} [options.enabled=true] - Enable/disable redaction
170
+ * @param {Object} [options.patterns=DEFAULT_PATTERNS] - Custom redaction patterns
171
+ * @param {Set<string>} [options.sensitiveFields] - Custom sensitive field names
172
+ * @param {boolean} [options.redactEmails=false] - Redact email addresses
173
+ * @param {boolean} [options.redactIPs=false] - Redact IP addresses
174
+ * @param {boolean} [options.trackStats=true] - Track redaction statistics
175
+ * @param {string} [options.redactionMarker='[REDACTED]'] - Default redaction marker
176
+ */
177
+ constructor(options = {}) {
178
+ this.enabled = options.enabled !== false;
179
+ this.patterns = { ...DEFAULT_PATTERNS, ...(options.patterns || {}) };
180
+ this.sensitiveFields = new Set([
181
+ ...SENSITIVE_FIELD_NAMES,
182
+ ...(options.sensitiveFields || []),
183
+ ]);
184
+ this.trackStats = options.trackStats !== false;
185
+ this.redactionMarker = options.redactionMarker || '[REDACTED]';
186
+
187
+ // Enable/disable optional patterns
188
+ if (options.redactEmails) {
189
+ this.patterns.email.enabled = true;
190
+ }
191
+ if (options.redactIPs) {
192
+ this.patterns.ipAddress.enabled = true;
193
+ }
194
+
195
+ // Statistics tracking
196
+ this.stats = {
197
+ totalRedactions: 0,
198
+ byPattern: {},
199
+ byFieldName: {},
200
+ totalProcessed: 0,
201
+ totalTimeMs: 0,
202
+ };
203
+
204
+ // Pre-compile regex patterns for better performance
205
+ this._compilePatterns();
206
+ }
207
+
208
+ /**
209
+ * Pre-compile regex patterns for performance
210
+ *
211
+ * @private
212
+ */
213
+ _compilePatterns() {
214
+ this.compiledPatterns = [];
215
+
216
+ for (const [name, pattern] of Object.entries(this.patterns)) {
217
+ if (pattern.enabled === false) continue;
218
+
219
+ this.compiledPatterns.push({
220
+ name,
221
+ regex: pattern.regex,
222
+ replacement: pattern.replacement,
223
+ description: pattern.description,
224
+ });
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Check if a field name is sensitive
230
+ *
231
+ * @private
232
+ * @param {string} fieldName - Field name to check
233
+ * @returns {boolean} True if field is sensitive
234
+ */
235
+ _isSensitiveField(fieldName) {
236
+ if (typeof fieldName !== 'string') return false;
237
+
238
+ const lowerName = fieldName.toLowerCase();
239
+
240
+ // Direct match
241
+ if (this.sensitiveFields.has(lowerName)) return true;
242
+
243
+ // Pattern match (contains sensitive keywords)
244
+ for (const sensitive of this.sensitiveFields) {
245
+ if (lowerName.includes(sensitive.toLowerCase())) {
246
+ return true;
247
+ }
248
+ }
249
+
250
+ return false;
251
+ }
252
+
253
+ /**
254
+ * Redact secrets from a string value
255
+ *
256
+ * @private
257
+ * @param {string} value - String to redact
258
+ * @returns {string} Redacted string
259
+ */
260
+ _redactString(value) {
261
+ if (typeof value !== 'string' || !value) return value;
262
+
263
+ let redacted = value;
264
+ let hasRedaction = false;
265
+
266
+ for (const pattern of this.compiledPatterns) {
267
+ const original = redacted;
268
+ redacted = redacted.replace(pattern.regex, pattern.replacement);
269
+
270
+ if (redacted !== original) {
271
+ hasRedaction = true;
272
+ if (this.trackStats) {
273
+ this.stats.totalRedactions++;
274
+ this.stats.byPattern[pattern.name] =
275
+ (this.stats.byPattern[pattern.name] || 0) + 1;
276
+ }
277
+ }
278
+ }
279
+
280
+ return redacted;
281
+ }
282
+
283
+ /**
284
+ * Redact secrets from any value (deep traversal)
285
+ *
286
+ * @private
287
+ * @param {*} value - Value to redact
288
+ * @param {string} [fieldName=''] - Current field name for context
289
+ * @param {Set} [seen=new Set()] - Circular reference detection
290
+ * @returns {*} Redacted value with preserved structure
291
+ */
292
+ _redactValue(value, fieldName = '', seen = new Set()) {
293
+ // Handle null/undefined
294
+ if (value == null) return value;
295
+
296
+ // Circular reference detection
297
+ if (typeof value === 'object') {
298
+ if (seen.has(value)) {
299
+ return '[Circular]';
300
+ }
301
+ seen.add(value);
302
+ }
303
+
304
+ // Check if field name is sensitive - redact entire value
305
+ if (fieldName && this._isSensitiveField(fieldName)) {
306
+ if (this.trackStats) {
307
+ this.stats.totalRedactions++;
308
+ this.stats.byFieldName[fieldName] =
309
+ (this.stats.byFieldName[fieldName] || 0) + 1;
310
+ }
311
+ return this.redactionMarker;
312
+ }
313
+
314
+ // Handle different types
315
+ if (typeof value === 'string') {
316
+ return this._redactString(value);
317
+ }
318
+
319
+ if (Array.isArray(value)) {
320
+ return value.map((item, index) =>
321
+ this._redactValue(item, `${fieldName}[${index}]`, new Set(seen)),
322
+ );
323
+ }
324
+
325
+ if (typeof value === 'object') {
326
+ const redacted = {};
327
+ for (const [key, val] of Object.entries(value)) {
328
+ const newFieldName = fieldName ? `${fieldName}.${key}` : key;
329
+ redacted[key] = this._redactValue(val, newFieldName, new Set(seen));
330
+ }
331
+ return redacted;
332
+ }
333
+
334
+ // Primitives (number, boolean, etc.) - return as-is
335
+ return value;
336
+ }
337
+
338
+ /**
339
+ * Securely log data with automatic secret redaction
340
+ *
341
+ * Main entry point for secure logging. Redacts sensitive data while
342
+ * preserving the structure of the original data.
343
+ *
344
+ * @param {*} data - Data to log (any type)
345
+ * @param {Object} [options={}] - Logging options
346
+ * @param {boolean} [options.skipRedaction=false] - Skip redaction for this call
347
+ * @param {string[]} [options.additionalFields=[]] - Additional sensitive field names
348
+ * @returns {*} Redacted data safe for logging
349
+ *
350
+ * @example
351
+ * const logger = new SecureLogger();
352
+ * const safeData = logger.secureLog({
353
+ * username: 'john',
354
+ * password: 'secret123',
355
+ * apiKey: 'abc123def456'
356
+ * });
357
+ * // Returns: { username: 'john', password: '[REDACTED]', apiKey: '[REDACTED]' }
358
+ */
359
+ secureLog(data, options = {}) {
360
+ const startTime = process.hrtime.bigint();
361
+
362
+ if (!this.enabled || options.skipRedaction) {
363
+ return data;
364
+ }
365
+
366
+ // Temporarily add additional sensitive fields
367
+ let originalFields = null;
368
+ if (options.additionalFields && Array.isArray(options.additionalFields)) {
369
+ originalFields = new Set(this.sensitiveFields);
370
+ options.additionalFields.forEach((field) =>
371
+ this.sensitiveFields.add(field),
372
+ );
373
+ }
374
+
375
+ try {
376
+ const redacted = this._redactValue(data);
377
+
378
+ if (this.trackStats) {
379
+ this.stats.totalProcessed++;
380
+ const endTime = process.hrtime.bigint();
381
+ const durationNs = Number(endTime - startTime);
382
+ this.stats.totalTimeMs += durationNs / 1000000; // Convert ns to ms
383
+ }
384
+
385
+ return redacted;
386
+ } finally {
387
+ // Restore original sensitive fields
388
+ if (originalFields !== null) {
389
+ this.sensitiveFields = originalFields;
390
+ }
391
+ }
392
+ }
393
+
394
+ /**
395
+ * Add a custom redaction pattern
396
+ *
397
+ * @param {string} name - Pattern name
398
+ * @param {Object} pattern - Pattern configuration
399
+ * @param {RegExp} pattern.regex - Regular expression to match
400
+ * @param {string|Function} pattern.replacement - Replacement string or function
401
+ * @param {string} [pattern.description=''] - Pattern description
402
+ * @param {boolean} [pattern.enabled=true] - Enable pattern
403
+ *
404
+ * @example
405
+ * logger.addPattern('customToken', {
406
+ * regex: /\bCUST-[A-Z0-9]{16}\b/g,
407
+ * replacement: '[REDACTED_CUSTOM_TOKEN]',
408
+ * description: 'Custom application tokens'
409
+ * });
410
+ */
411
+ addPattern(name, pattern) {
412
+ this.patterns[name] = {
413
+ regex: pattern.regex,
414
+ replacement: pattern.replacement,
415
+ description: pattern.description || '',
416
+ enabled: pattern.enabled !== false,
417
+ };
418
+ this._compilePatterns();
419
+ }
420
+
421
+ /**
422
+ * Remove a redaction pattern
423
+ *
424
+ * @param {string} name - Pattern name to remove
425
+ * @returns {boolean} True if pattern was removed
426
+ */
427
+ removePattern(name) {
428
+ if (this.patterns[name]) {
429
+ delete this.patterns[name];
430
+ this._compilePatterns();
431
+ return true;
432
+ }
433
+ return false;
434
+ }
435
+
436
+ /**
437
+ * Add sensitive field names
438
+ *
439
+ * @param {string|string[]} fields - Field name(s) to add
440
+ *
441
+ * @example
442
+ * logger.addSensitiveFields(['ssn', 'taxId']);
443
+ */
444
+ addSensitiveFields(fields) {
445
+ const fieldsArray = Array.isArray(fields) ? fields : [fields];
446
+ fieldsArray.forEach((field) => this.sensitiveFields.add(field));
447
+ }
448
+
449
+ /**
450
+ * Remove sensitive field names
451
+ *
452
+ * @param {string|string[]} fields - Field name(s) to remove
453
+ * @returns {boolean} True if any fields were removed
454
+ */
455
+ removeSensitiveFields(fields) {
456
+ const fieldsArray = Array.isArray(fields) ? fields : [fields];
457
+ let removed = false;
458
+ fieldsArray.forEach((field) => {
459
+ if (this.sensitiveFields.delete(field)) {
460
+ removed = true;
461
+ }
462
+ });
463
+ return removed;
464
+ }
465
+
466
+ /**
467
+ * Get redaction statistics
468
+ *
469
+ * @returns {Object} Statistics object
470
+ * @returns {number} stats.totalRedactions - Total redactions performed
471
+ * @returns {number} stats.totalProcessed - Total items processed
472
+ * @returns {number} stats.totalTimeMs - Total processing time in ms
473
+ * @returns {number} stats.averageTimeMs - Average time per item
474
+ * @returns {Object} stats.byPattern - Redactions by pattern name
475
+ * @returns {Object} stats.byFieldName - Redactions by field name
476
+ */
477
+ getStats() {
478
+ return {
479
+ ...this.stats,
480
+ averageTimeMs:
481
+ this.stats.totalProcessed > 0
482
+ ? this.stats.totalTimeMs / this.stats.totalProcessed
483
+ : 0,
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Reset statistics
489
+ */
490
+ resetStats() {
491
+ this.stats = {
492
+ totalRedactions: 0,
493
+ byPattern: {},
494
+ byFieldName: {},
495
+ totalProcessed: 0,
496
+ totalTimeMs: 0,
497
+ };
498
+ }
499
+
500
+ /**
501
+ * Enable or disable redaction
502
+ *
503
+ * @param {boolean} enabled - Enable state
504
+ */
505
+ setEnabled(enabled) {
506
+ this.enabled = !!enabled;
507
+ }
508
+
509
+ /**
510
+ * Check if redaction is enabled
511
+ *
512
+ * @returns {boolean} True if enabled
513
+ */
514
+ isEnabled() {
515
+ return this.enabled;
516
+ }
517
+
518
+ /**
519
+ * Get list of active patterns
520
+ *
521
+ * @returns {Array<Object>} Array of pattern metadata
522
+ */
523
+ getActivePatterns() {
524
+ return this.compiledPatterns.map((p) => ({
525
+ name: p.name,
526
+ description: p.description,
527
+ }));
528
+ }
529
+
530
+ /**
531
+ * Get list of sensitive field names
532
+ *
533
+ * @returns {string[]} Array of sensitive field names
534
+ */
535
+ getSensitiveFields() {
536
+ return Array.from(this.sensitiveFields);
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Create a secure log function with bound logger instance
542
+ *
543
+ * @param {Object} options - SecureLogger options
544
+ * @returns {Function} Bound secureLog function
545
+ *
546
+ * @example
547
+ * const secureLog = createSecureLog({ redactEmails: true });
548
+ * const safe = secureLog({ email: 'user@example.com' });
549
+ */
550
+ function createSecureLog(options = {}) {
551
+ const logger = new SecureLogger(options);
552
+ return (data, logOptions) => logger.secureLog(data, logOptions);
553
+ }
554
+
555
+ // Create default singleton instance
556
+ const defaultLogger = new SecureLogger();
557
+
558
+ /**
559
+ * Default secure log function using singleton logger
560
+ *
561
+ * @param {*} data - Data to log
562
+ * @param {Object} [options={}] - Logging options
563
+ * @returns {*} Redacted data
564
+ */
565
+ function secureLog(data, options = {}) {
566
+ return defaultLogger.secureLog(data, options);
567
+ }
568
+
569
+ module.exports = {
570
+ SecureLogger,
571
+ createSecureLog,
572
+ secureLog,
573
+ defaultLogger,
574
+ DEFAULT_PATTERNS,
575
+ SENSITIVE_FIELD_NAMES,
576
+ };