@devilsdev/rag-pipeline-utils 2.2.0 → 2.3.0

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 (46) hide show
  1. package/README.md +323 -722
  2. package/contracts/contract-schema.json +189 -0
  3. package/contracts/reranker-contract.json +69 -0
  4. package/dist/index.cjs +19 -0
  5. package/dist/index.mjs +15 -1
  6. package/package.json +4 -2
  7. package/src/cli/commands/wizard.js +627 -0
  8. package/src/cli/input-sanitization-middleware.js +356 -0
  9. package/src/cli/templates/development.template.json +64 -0
  10. package/src/cli/templates/minimal.template.json +33 -0
  11. package/src/cli/templates/production.template.json +104 -0
  12. package/src/cli/templates/testing.template.json +45 -0
  13. package/src/config/connection-pool-config.json +259 -0
  14. package/src/config/memory-optimization.js +414 -0
  15. package/src/config/redaction-patterns.json +150 -0
  16. package/src/config/sanitization-rules.json +323 -0
  17. package/src/core/performance/streaming-safeguards.js +10 -1
  18. package/src/core/plugin-registry.js +275 -0
  19. package/src/dag/core/error-context.js +391 -0
  20. package/src/dag/core/execution-scheduler.js +350 -0
  21. package/src/dag/core/topology-validation.js +415 -0
  22. package/src/dag/dag-engine.js +26 -424
  23. package/src/dev/README.md +126 -0
  24. package/src/dev/dev-server.js +356 -0
  25. package/src/dev/examples/hot-reload-usage.js +355 -0
  26. package/src/dev/hot-reload.js +807 -0
  27. package/src/enterprise/sso-integration.js +184 -15
  28. package/src/index.js +19 -0
  29. package/src/observability/event-logger.js +62 -2
  30. package/src/plugins/optimized-openai-embedder.js +169 -0
  31. package/src/reranker/llm-reranker.js +30 -4
  32. package/src/security/jwt-config.js +431 -0
  33. package/src/security/jwt-validator.js +701 -0
  34. package/src/utils/api-client-pool-integration.js +436 -0
  35. package/src/utils/batch-processor.js +710 -0
  36. package/src/utils/connection-pool.js +605 -0
  37. package/src/utils/contract-validator.js +460 -0
  38. package/src/utils/error-formatter.js +603 -0
  39. package/src/utils/examples/error-formatter-usage.js +351 -0
  40. package/src/utils/input-sanitizer.js +967 -0
  41. package/src/utils/memory-monitor.js +800 -0
  42. package/src/utils/rate-limiter.js +244 -0
  43. package/src/utils/secure-logger-config.js +352 -0
  44. package/src/utils/secure-logger.js +576 -0
  45. package/src/utils/secure-logging.js +338 -0
  46. package/src/utils/structured-logger.js +32 -5
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Rate Limiter Utility
3
+ * Protects against brute force attacks and abuse
4
+ *
5
+ * Features:
6
+ * - Sliding window rate limiting
7
+ * - Per-identifier tracking (IP, user, tenant)
8
+ * - Automatic cleanup of expired entries
9
+ * - Configurable thresholds and windows
10
+ */
11
+
12
+ const crypto = require('crypto');
13
+
14
+ class RateLimiter {
15
+ /**
16
+ * Create a rate limiter instance
17
+ * @param {Object} options - Configuration options
18
+ * @param {number} options.maxAttempts - Maximum attempts allowed (default: 5)
19
+ * @param {number} options.windowMs - Time window in milliseconds (default: 15 minutes)
20
+ * @param {number} options.blockDurationMs - How long to block after exceeding limit (default: 1 hour)
21
+ * @param {number} options.cleanupIntervalMs - How often to cleanup expired entries (default: 5 minutes)
22
+ */
23
+ constructor(options = {}) {
24
+ this.maxAttempts = options.maxAttempts || 5;
25
+ this.windowMs = options.windowMs || 15 * 60 * 1000; // 15 minutes
26
+ this.blockDurationMs = options.blockDurationMs || 60 * 60 * 1000; // 1 hour
27
+ this.cleanupIntervalMs = options.cleanupIntervalMs || 5 * 60 * 1000; // 5 minutes
28
+
29
+ // Map of identifier -> { attempts: [], blockedUntil: timestamp }
30
+ this.store = new Map();
31
+
32
+ // Start automatic cleanup
33
+ this._startCleanup();
34
+ }
35
+
36
+ /**
37
+ * Check if a request should be allowed
38
+ * @param {string} identifier - Unique identifier (IP, userId, tenantId, etc.)
39
+ * @returns {Object} { allowed: boolean, remaining: number, resetAt: Date, blockedUntil: Date|null }
40
+ */
41
+ allowRequest(identifier) {
42
+ const key = this._hashIdentifier(identifier);
43
+ const now = Date.now();
44
+
45
+ // Get or create record
46
+ let record = this.store.get(key);
47
+ if (!record) {
48
+ record = { attempts: [], blockedUntil: null };
49
+ this.store.set(key, record);
50
+ }
51
+
52
+ // Check if currently blocked
53
+ if (record.blockedUntil && record.blockedUntil > now) {
54
+ return {
55
+ allowed: false,
56
+ remaining: 0,
57
+ resetAt: new Date(record.blockedUntil),
58
+ blockedUntil: new Date(record.blockedUntil),
59
+ retryAfter: Math.ceil((record.blockedUntil - now) / 1000), // seconds
60
+ };
61
+ }
62
+
63
+ // Clear expired block
64
+ if (record.blockedUntil && record.blockedUntil <= now) {
65
+ record.blockedUntil = null;
66
+ }
67
+
68
+ // Remove expired attempts
69
+ const windowStart = now - this.windowMs;
70
+ record.attempts = record.attempts.filter(
71
+ (timestamp) => timestamp > windowStart,
72
+ );
73
+
74
+ // Check if exceeded limit
75
+ if (record.attempts.length >= this.maxAttempts) {
76
+ // Block the identifier
77
+ record.blockedUntil = now + this.blockDurationMs;
78
+ return {
79
+ allowed: false,
80
+ remaining: 0,
81
+ resetAt: new Date(record.blockedUntil),
82
+ blockedUntil: new Date(record.blockedUntil),
83
+ retryAfter: Math.ceil(this.blockDurationMs / 1000), // seconds
84
+ };
85
+ }
86
+
87
+ // Allow request and record attempt
88
+ record.attempts.push(now);
89
+
90
+ return {
91
+ allowed: true,
92
+ remaining: this.maxAttempts - record.attempts.length,
93
+ resetAt: new Date(now + this.windowMs),
94
+ blockedUntil: null,
95
+ retryAfter: 0,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Manually reset rate limit for an identifier
101
+ * @param {string} identifier - Identifier to reset
102
+ */
103
+ reset(identifier) {
104
+ const key = this._hashIdentifier(identifier);
105
+ this.store.delete(key);
106
+ }
107
+
108
+ /**
109
+ * Get current status for an identifier
110
+ * @param {string} identifier - Identifier to check
111
+ * @returns {Object} Current rate limit status
112
+ */
113
+ getStatus(identifier) {
114
+ const key = this._hashIdentifier(identifier);
115
+ const record = this.store.get(key);
116
+ const now = Date.now();
117
+
118
+ if (!record) {
119
+ return {
120
+ attempts: 0,
121
+ remaining: this.maxAttempts,
122
+ blocked: false,
123
+ blockedUntil: null,
124
+ };
125
+ }
126
+
127
+ // Remove expired attempts
128
+ const windowStart = now - this.windowMs;
129
+ const validAttempts = record.attempts.filter(
130
+ (timestamp) => timestamp > windowStart,
131
+ );
132
+
133
+ const blocked = !!(record.blockedUntil && record.blockedUntil > now);
134
+
135
+ return {
136
+ attempts: validAttempts.length,
137
+ remaining: Math.max(0, this.maxAttempts - validAttempts.length),
138
+ blocked,
139
+ blockedUntil: blocked ? new Date(record.blockedUntil) : null,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Clean up expired entries
145
+ */
146
+ cleanup() {
147
+ const now = Date.now();
148
+ const expiredKeys = [];
149
+
150
+ for (const [key, record] of this.store.entries()) {
151
+ // Check if block has expired and no recent attempts
152
+ const blockExpired = !record.blockedUntil || record.blockedUntil < now;
153
+ const windowStart = now - this.windowMs;
154
+ const hasRecentAttempts = record.attempts.some(
155
+ (timestamp) => timestamp > windowStart,
156
+ );
157
+
158
+ if (blockExpired && !hasRecentAttempts) {
159
+ expiredKeys.push(key);
160
+ }
161
+ }
162
+
163
+ // Delete expired entries
164
+ expiredKeys.forEach((key) => this.store.delete(key));
165
+
166
+ return {
167
+ cleaned: expiredKeys.length,
168
+ remaining: this.store.size,
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Get statistics about rate limiter usage
174
+ * @returns {Object} Statistics
175
+ */
176
+ getStats() {
177
+ const now = Date.now();
178
+ let totalAttempts = 0;
179
+ let blockedIdentifiers = 0;
180
+ let activeIdentifiers = 0;
181
+
182
+ for (const record of this.store.values()) {
183
+ const windowStart = now - this.windowMs;
184
+ const validAttempts = record.attempts.filter(
185
+ (timestamp) => timestamp > windowStart,
186
+ );
187
+
188
+ if (validAttempts.length > 0) {
189
+ activeIdentifiers++;
190
+ totalAttempts += validAttempts.length;
191
+ }
192
+
193
+ if (record.blockedUntil && record.blockedUntil > now) {
194
+ blockedIdentifiers++;
195
+ }
196
+ }
197
+
198
+ return {
199
+ totalIdentifiers: this.store.size,
200
+ activeIdentifiers,
201
+ blockedIdentifiers,
202
+ totalAttempts,
203
+ averageAttemptsPerIdentifier:
204
+ activeIdentifiers > 0 ? totalAttempts / activeIdentifiers : 0,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Stop the rate limiter and cleanup
210
+ */
211
+ destroy() {
212
+ if (this._cleanupInterval) {
213
+ clearInterval(this._cleanupInterval);
214
+ this._cleanupInterval = null;
215
+ }
216
+ this.store.clear();
217
+ }
218
+
219
+ /**
220
+ * Hash identifier for storage (privacy)
221
+ * @private
222
+ */
223
+ _hashIdentifier(identifier) {
224
+ return crypto.createHash('sha256').update(identifier).digest('hex');
225
+ }
226
+
227
+ /**
228
+ * Start automatic cleanup timer
229
+ * @private
230
+ */
231
+ _startCleanup() {
232
+ this._cleanupInterval = setInterval(() => {
233
+ this.cleanup();
234
+ }, this.cleanupIntervalMs);
235
+
236
+ // Don't prevent process exit
237
+ if (this._cleanupInterval.unref) {
238
+ this._cleanupInterval.unref();
239
+ }
240
+ }
241
+ }
242
+
243
+ module.exports = RateLimiter;
244
+ module.exports.default = module.exports;
@@ -0,0 +1,352 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Secure Logger Configuration Loader
5
+ *
6
+ * Loads and applies redaction pattern configurations from JSON files
7
+ * or JavaScript objects to create configured SecureLogger instances.
8
+ *
9
+ * @module secure-logger-config
10
+ * @since 2.2.5
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const { SecureLogger } = require('./secure-logger');
16
+
17
+ /**
18
+ * Load configuration from JSON file
19
+ *
20
+ * @param {string} configPath - Path to configuration file
21
+ * @returns {Object} Configuration object
22
+ * @throws {Error} If file cannot be read or parsed
23
+ *
24
+ * @private
25
+ */
26
+ function loadConfigFile(configPath) {
27
+ try {
28
+ const content = fs.readFileSync(configPath, 'utf8');
29
+ return JSON.parse(content);
30
+ } catch (error) {
31
+ throw new Error(
32
+ `Failed to load redaction config from ${configPath}: ${error.message}`,
33
+ );
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Convert regex string to RegExp object
39
+ *
40
+ * @param {string} regexStr - Regex pattern as string
41
+ * @param {string} flags - Regex flags
42
+ * @returns {RegExp} Compiled regular expression
43
+ *
44
+ * @private
45
+ */
46
+ function compileRegex(regexStr, flags = 'g') {
47
+ try {
48
+ return new RegExp(regexStr, flags);
49
+ } catch (error) {
50
+ throw new Error(`Invalid regex pattern: ${regexStr} - ${error.message}`);
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Apply environment-specific configuration overrides
56
+ *
57
+ * @param {Object} config - Base configuration
58
+ * @param {string} [environment] - Environment name (defaults to NODE_ENV)
59
+ * @returns {Object} Configuration with environment overrides applied
60
+ *
61
+ * @private
62
+ */
63
+ function applyEnvironmentOverrides(config, environment) {
64
+ const env = environment || process.env.NODE_ENV || 'development';
65
+
66
+ if (config.environments && config.environments[env]) {
67
+ return {
68
+ ...config,
69
+ ...config.environments[env],
70
+ };
71
+ }
72
+
73
+ return config;
74
+ }
75
+
76
+ /**
77
+ * Convert custom pattern configuration to SecureLogger format
78
+ *
79
+ * @param {Object} customPatterns - Custom patterns from config
80
+ * @returns {Object} Patterns in SecureLogger format
81
+ *
82
+ * @private
83
+ */
84
+ function convertCustomPatterns(customPatterns) {
85
+ const converted = {};
86
+
87
+ for (const [name, pattern] of Object.entries(customPatterns)) {
88
+ converted[name] = {
89
+ regex: compileRegex(pattern.regex, pattern.flags || 'g'),
90
+ replacement: pattern.replacement,
91
+ description: pattern.description || '',
92
+ enabled: pattern.enabled !== false,
93
+ };
94
+ }
95
+
96
+ return converted;
97
+ }
98
+
99
+ /**
100
+ * Create SecureLogger from configuration
101
+ *
102
+ * @param {Object|string} config - Configuration object or path to config file
103
+ * @param {string} [environment] - Environment name for overrides
104
+ * @returns {SecureLogger} Configured SecureLogger instance
105
+ *
106
+ * @example
107
+ * // Load from file
108
+ * const logger = createLoggerFromConfig('./redaction-config.json');
109
+ *
110
+ * // Load from object
111
+ * const logger = createLoggerFromConfig({
112
+ * enabled: true,
113
+ * redactEmails: true,
114
+ * customPatterns: {
115
+ * myPattern: {
116
+ * regex: '\\bMY-[A-Z0-9]+\\b',
117
+ * replacement: '[MY_TOKEN]'
118
+ * }
119
+ * }
120
+ * });
121
+ *
122
+ * // Load with environment override
123
+ * const logger = createLoggerFromConfig(config, 'production');
124
+ */
125
+ function createLoggerFromConfig(config, environment) {
126
+ let configuration;
127
+
128
+ // Load from file if string path provided
129
+ if (typeof config === 'string') {
130
+ configuration = loadConfigFile(config);
131
+ } else {
132
+ configuration = config;
133
+ }
134
+
135
+ // Apply environment-specific overrides
136
+ configuration = applyEnvironmentOverrides(configuration, environment);
137
+
138
+ // Build SecureLogger options
139
+ const options = {
140
+ enabled: configuration.enabled !== false,
141
+ trackStats: configuration.trackStats !== false,
142
+ redactionMarker: configuration.redactionMarker,
143
+ redactEmails: configuration.redactEmails || false,
144
+ redactIPs: configuration.redactIPs || false,
145
+ };
146
+
147
+ // Add custom patterns
148
+ if (configuration.customPatterns) {
149
+ options.patterns = convertCustomPatterns(configuration.customPatterns);
150
+ }
151
+
152
+ // Add sensitive fields
153
+ if (
154
+ configuration.sensitiveFields &&
155
+ Array.isArray(configuration.sensitiveFields)
156
+ ) {
157
+ options.sensitiveFields = configuration.sensitiveFields;
158
+ }
159
+
160
+ // Create logger instance
161
+ const logger = new SecureLogger(options);
162
+
163
+ // Handle enabled/disabled patterns
164
+ if (
165
+ configuration.disabledPatterns &&
166
+ Array.isArray(configuration.disabledPatterns)
167
+ ) {
168
+ configuration.disabledPatterns.forEach((patternName) => {
169
+ logger.removePattern(patternName);
170
+ });
171
+ }
172
+
173
+ if (
174
+ configuration.enabledPatterns &&
175
+ Array.isArray(configuration.enabledPatterns)
176
+ ) {
177
+ // If specific patterns are listed, disable all others
178
+ const activePatterns = logger.getActivePatterns();
179
+ activePatterns.forEach((pattern) => {
180
+ if (!configuration.enabledPatterns.includes(pattern.name)) {
181
+ logger.removePattern(pattern.name);
182
+ }
183
+ });
184
+ }
185
+
186
+ return logger;
187
+ }
188
+
189
+ /**
190
+ * Load default configuration
191
+ *
192
+ * Attempts to load configuration from default locations:
193
+ * 1. ./redaction-config.json (current directory)
194
+ * 2. ./config/redaction-patterns.json
195
+ * 3. src/config/redaction-patterns.json
196
+ *
197
+ * @param {string} [environment] - Environment name for overrides
198
+ * @returns {SecureLogger} Configured logger or default logger if no config found
199
+ *
200
+ * @example
201
+ * const logger = loadDefaultConfig();
202
+ * const prodLogger = loadDefaultConfig('production');
203
+ */
204
+ function loadDefaultConfig(environment) {
205
+ const defaultPaths = [
206
+ path.join(process.cwd(), 'redaction-config.json'),
207
+ path.join(process.cwd(), 'config', 'redaction-patterns.json'),
208
+ path.join(__dirname, '..', 'config', 'redaction-patterns.json'),
209
+ ];
210
+
211
+ for (const configPath of defaultPaths) {
212
+ if (fs.existsSync(configPath)) {
213
+ try {
214
+ return createLoggerFromConfig(configPath, environment);
215
+ } catch (error) {
216
+ console.warn(
217
+ `Failed to load config from ${configPath}:`,
218
+ error.message,
219
+ );
220
+ }
221
+ }
222
+ }
223
+
224
+ // Return default logger if no config found
225
+ return new SecureLogger();
226
+ }
227
+
228
+ /**
229
+ * Validate configuration against schema
230
+ *
231
+ * @param {Object} config - Configuration to validate
232
+ * @returns {Object} Validation result { valid: boolean, errors: string[] }
233
+ *
234
+ * @example
235
+ * const result = validateConfig(myConfig);
236
+ * if (!result.valid) {
237
+ * console.error('Config errors:', result.errors);
238
+ * }
239
+ */
240
+ function validateConfig(config) {
241
+ const errors = [];
242
+
243
+ // Check required fields
244
+ if (config.enabled !== undefined && typeof config.enabled !== 'boolean') {
245
+ errors.push('enabled must be a boolean');
246
+ }
247
+
248
+ if (
249
+ config.trackStats !== undefined &&
250
+ typeof config.trackStats !== 'boolean'
251
+ ) {
252
+ errors.push('trackStats must be a boolean');
253
+ }
254
+
255
+ if (
256
+ config.redactionMarker !== undefined &&
257
+ typeof config.redactionMarker !== 'string'
258
+ ) {
259
+ errors.push('redactionMarker must be a string');
260
+ }
261
+
262
+ // Validate custom patterns
263
+ if (config.customPatterns && typeof config.customPatterns === 'object') {
264
+ for (const [name, pattern] of Object.entries(config.customPatterns)) {
265
+ if (!pattern.regex) {
266
+ errors.push(`Custom pattern '${name}' missing regex field`);
267
+ }
268
+ if (!pattern.replacement) {
269
+ errors.push(`Custom pattern '${name}' missing replacement field`);
270
+ }
271
+
272
+ // Try to compile regex
273
+ if (pattern.regex) {
274
+ try {
275
+ compileRegex(pattern.regex, pattern.flags || 'g');
276
+ } catch (error) {
277
+ errors.push(
278
+ `Custom pattern '${name}' has invalid regex: ${error.message}`,
279
+ );
280
+ }
281
+ }
282
+ }
283
+ }
284
+
285
+ // Validate sensitive fields
286
+ if (config.sensitiveFields !== undefined) {
287
+ if (!Array.isArray(config.sensitiveFields)) {
288
+ errors.push('sensitiveFields must be an array');
289
+ } else {
290
+ config.sensitiveFields.forEach((field, index) => {
291
+ if (typeof field !== 'string') {
292
+ errors.push(`sensitiveFields[${index}] must be a string`);
293
+ }
294
+ });
295
+ }
296
+ }
297
+
298
+ return {
299
+ valid: errors.length === 0,
300
+ errors,
301
+ };
302
+ }
303
+
304
+ /**
305
+ * Create configuration template
306
+ *
307
+ * @returns {Object} Template configuration object
308
+ *
309
+ * @example
310
+ * const template = createConfigTemplate();
311
+ * // Modify template as needed
312
+ * const logger = createLoggerFromConfig(template);
313
+ */
314
+ function createConfigTemplate() {
315
+ return {
316
+ enabled: true,
317
+ trackStats: true,
318
+ redactionMarker: '[REDACTED]',
319
+ redactEmails: false,
320
+ redactIPs: false,
321
+ customPatterns: {},
322
+ sensitiveFields: [],
323
+ disabledPatterns: [],
324
+ enabledPatterns: [],
325
+ environments: {
326
+ development: {
327
+ enabled: false,
328
+ trackStats: false,
329
+ },
330
+ production: {
331
+ enabled: true,
332
+ trackStats: true,
333
+ redactEmails: true,
334
+ redactIPs: true,
335
+ },
336
+ test: {
337
+ enabled: false,
338
+ },
339
+ },
340
+ };
341
+ }
342
+
343
+ module.exports = {
344
+ createLoggerFromConfig,
345
+ loadDefaultConfig,
346
+ validateConfig,
347
+ createConfigTemplate,
348
+ loadConfigFile,
349
+ compileRegex,
350
+ applyEnvironmentOverrides,
351
+ convertCustomPatterns,
352
+ };