@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.
- package/README.md +260 -19
- package/contracts/contract-schema.json +189 -0
- package/dist/index.cjs +19 -0
- package/dist/index.mjs +15 -1
- package/package.json +3 -2
- package/src/cli/commands/wizard.js +627 -0
- package/src/cli/input-sanitization-middleware.js +356 -0
- package/src/cli/templates/development.template.json +64 -0
- package/src/cli/templates/minimal.template.json +33 -0
- package/src/cli/templates/production.template.json +104 -0
- package/src/cli/templates/testing.template.json +45 -0
- package/src/config/connection-pool-config.json +259 -0
- package/src/config/memory-optimization.js +414 -0
- package/src/config/redaction-patterns.json +150 -0
- package/src/config/sanitization-rules.json +323 -0
- package/src/core/plugin-registry.js +266 -20
- package/src/dag/core/error-context.js +391 -0
- package/src/dag/core/execution-scheduler.js +350 -0
- package/src/dag/core/topology-validation.js +415 -0
- package/src/dag/dag-engine.js +26 -424
- package/src/dev/README.md +126 -0
- package/src/dev/dev-server.js +356 -0
- package/src/dev/examples/hot-reload-usage.js +355 -0
- package/src/dev/hot-reload.js +807 -0
- package/src/enterprise/sso-integration.js +67 -25
- package/src/index.js +19 -0
- package/src/observability/event-logger.js +62 -2
- package/src/plugins/optimized-openai-embedder.js +169 -0
- package/src/security/jwt-config.js +431 -0
- package/src/security/jwt-validator.js +701 -0
- package/src/utils/api-client-pool-integration.js +436 -0
- package/src/utils/batch-processor.js +710 -0
- package/src/utils/connection-pool.js +605 -0
- package/src/utils/contract-validator.js +460 -0
- package/src/utils/error-formatter.js +603 -0
- package/src/utils/examples/error-formatter-usage.js +351 -0
- package/src/utils/input-sanitizer.js +967 -0
- package/src/utils/memory-monitor.js +800 -0
- package/src/utils/secure-logger-config.js +352 -0
- 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
|
+
};
|