@devilsdev/rag-pipeline-utils 2.1.12 → 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.
Files changed (113) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +178 -550
  3. package/bin/cli.js +8 -14
  4. package/contracts/reranker-contract.json +69 -0
  5. package/dist/index.d.ts +237 -0
  6. package/dist/index.mjs +22 -7
  7. package/package.json +18 -13
  8. package/src/ai/adaptive-retrieval.js +191 -0
  9. package/src/ai/federated-learning.js +214 -0
  10. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  11. package/src/ai/federation.js +76 -0
  12. package/src/ai/index-fixed.js +509 -0
  13. package/src/ai/index.js +33 -0
  14. package/src/ai/model-trainer.js +217 -0
  15. package/src/ai/model-training.js +205 -0
  16. package/src/ai/module-template.js +37 -0
  17. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  18. package/src/ai/multimodal-processing.js +268 -0
  19. package/src/ai/multimodal.js +71 -0
  20. package/src/ai/orchestrator.js +70 -0
  21. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  22. package/src/ai/retrieval-engine.js +69 -0
  23. package/src/ai/training/model-training-orchestrator.js +228 -0
  24. package/src/ci/workflow-analyzer.js +71 -0
  25. package/src/cli/commands/ai-ml.js +967 -0
  26. package/src/cli/commands/docs.js +228 -0
  27. package/src/cli/commands/dx.js +538 -0
  28. package/src/cli/commands/plugin-hub.js +875 -0
  29. package/src/cli/doctor-command.js +639 -0
  30. package/src/cli/enhanced-cli-commands.js +945 -0
  31. package/src/cli/interactive-wizard.js +997 -0
  32. package/src/cli/plugin-marketplace-commands.js +869 -0
  33. package/src/cli.js +17 -0
  34. package/src/config/enhanced-ragrc-schema.js +632 -0
  35. package/src/config/load-config.js +54 -0
  36. package/src/config/load-plugin-config.js +69 -0
  37. package/src/config/normalize-config.js +105 -0
  38. package/src/config/schema-validator.js +315 -0
  39. package/src/config/validate-plugin-schema.js +35 -0
  40. package/src/config/validate-schema.js +113 -0
  41. package/src/contracts/plugin-contracts.js +33 -0
  42. package/src/core/create-pipeline.js +99 -0
  43. package/src/core/dependency-injection.js +242 -0
  44. package/src/core/observability/event-logger.js +241 -0
  45. package/src/core/observability/instrumented-pipeline.js +609 -0
  46. package/src/core/observability/metrics.js +497 -0
  47. package/src/core/observability/tracing.js +781 -0
  48. package/src/core/performance/benchmark.js +516 -0
  49. package/src/core/performance/parallel-processor.js +240 -0
  50. package/src/core/performance/streaming-safeguards.js +620 -0
  51. package/src/core/pipeline-factory.js +60 -0
  52. package/src/core/plugin-contracts.js +89 -0
  53. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  54. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  55. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  56. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  57. package/src/core/plugin-registry.js +294 -0
  58. package/src/dag/dag-engine.js +954 -0
  59. package/src/dag/dag-node.js +76 -0
  60. package/src/dag/enhanced-dag-engine.js +822 -0
  61. package/src/dx/index.js +40 -0
  62. package/src/dx/integration-templates.js +665 -0
  63. package/src/dx/performance-profiler.js +968 -0
  64. package/src/dx/realtime-debugger.js +755 -0
  65. package/src/dx/visual-pipeline-builder.js +578 -0
  66. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  67. package/src/ecosystem/plugin-certification.js +580 -0
  68. package/src/ecosystem/plugin-hub.js +821 -0
  69. package/src/enterprise/audit-logging.js +967 -0
  70. package/src/enterprise/data-governance.js +408 -0
  71. package/src/enterprise/multi-tenancy.js +496 -0
  72. package/src/enterprise/sso-integration.js +1030 -0
  73. package/src/evaluate/evaluator.js +52 -0
  74. package/src/evaluate/scoring.js +91 -0
  75. package/src/index.js +67 -0
  76. package/src/ingest.js +59 -0
  77. package/src/loader/csv-loader.js +38 -0
  78. package/src/loader/directory-loader.js +48 -0
  79. package/src/loader/html-loader.js +62 -0
  80. package/src/loader/markdown-loader.js +67 -0
  81. package/src/mocks/openai-embedder.js +119 -0
  82. package/src/mocks/openai-llm.js +77 -0
  83. package/src/mocks/pdf-loader.js +21 -0
  84. package/src/mocks/pinecone-retriever.js +106 -0
  85. package/src/observability/audit-logger.js +758 -0
  86. package/src/observability/dashboard-config.json +675 -0
  87. package/src/observability/event-logger.js +310 -0
  88. package/src/observability/metrics.js +691 -0
  89. package/src/observability/security-monitor.js +830 -0
  90. package/src/observability/slo-monitor.js +319 -0
  91. package/src/observability/telemetry.js +686 -0
  92. package/src/pipeline/pipeline.js +18 -0
  93. package/src/plugins/sample-embedder.js +53 -0
  94. package/src/plugins/sample-llm.js +30 -0
  95. package/src/plugins/sample-loader.js +24 -0
  96. package/src/plugins/sample-retriever.js +84 -0
  97. package/src/query.js +117 -0
  98. package/src/reranker/llm-reranker.js +111 -0
  99. package/src/scripts/script-utilities.js +36 -0
  100. package/src/security/plugin-sandbox.js +586 -0
  101. package/src/security/plugin-signature-verifier.js +414 -0
  102. package/src/security/validator.js +36 -0
  103. package/src/utils/ci/diagnostic-reporter.js +57 -0
  104. package/src/utils/logger.js +211 -0
  105. package/src/utils/performance-monitor.js +561 -0
  106. package/src/utils/plugin-scaffolder.js +447 -0
  107. package/src/utils/rate-limiter.js +244 -0
  108. package/src/utils/retry-policy.js +443 -0
  109. package/src/utils/retry.js +107 -0
  110. package/src/utils/safe-expression-evaluator.js +579 -0
  111. package/src/utils/secure-logging.js +338 -0
  112. package/src/utils/structured-logger.js +388 -0
  113. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,586 @@
1
+ /**
2
+ * @fileoverview Plugin sandboxing system for secure execution of third-party plugins.
3
+ * Implements isolated-vm based isolation with resource limits, permission controls, and audit trails.
4
+ *
5
+ * @author DevilsDev Team
6
+ * @since 2.1.0
7
+ * @version 2.1.1
8
+ */
9
+
10
+ const ivm = require('isolated-vm');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const crypto = require('crypto');
14
+ const { EventEmitter } = require('events');
15
+ const logger = require('../utils/structured-logger');
16
+ const PluginSignatureVerifier = require('./plugin-signature-verifier');
17
+
18
+ /**
19
+ * Input sanitizer for security validation
20
+ */
21
+ class InputSanitizer {
22
+ static sanitizeInput(input) {
23
+ if (typeof input !== 'string') {
24
+ throw new Error('Input must be a string');
25
+ }
26
+
27
+ // Remove potential script injections
28
+ const sanitized = input
29
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
30
+ .replace(/javascript:/gi, '')
31
+ .replace(/on\w+\s*=/gi, '')
32
+ .replace(/eval\s*\(/gi, '')
33
+ .replace(/Function\s*\(/gi, '');
34
+
35
+ return sanitized;
36
+ }
37
+
38
+ static validateInput(input) {
39
+ const maxLength = 10000;
40
+ const allowedChars = /^[a-zA-Z0-9\s.,?!;:\-_'"()[\]{}@#$%^&*+=|\\/<>~`]*$/;
41
+
42
+ if (input.length > maxLength) {
43
+ throw new Error('Input exceeds maximum length');
44
+ }
45
+
46
+ if (!allowedChars.test(input)) {
47
+ throw new Error('Input contains invalid characters');
48
+ }
49
+
50
+ return true;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Output sanitizer for data protection
56
+ */
57
+ class OutputSanitizer {
58
+ static sanitizeOutput(output) {
59
+ if (typeof output !== 'object' || !output) {
60
+ return { error: 'Invalid output format' };
61
+ }
62
+
63
+ const sanitized = { ...output };
64
+
65
+ // Remove sensitive fields
66
+ delete sanitized.apiKey;
67
+ delete sanitized.password;
68
+ delete sanitized.token;
69
+ delete sanitized.secret;
70
+
71
+ // Sanitize text content
72
+ if (sanitized.text) {
73
+ sanitized.text = this.sanitizeText(sanitized.text);
74
+ }
75
+
76
+ return sanitized;
77
+ }
78
+
79
+ static sanitizeText(text) {
80
+ return text
81
+ .replace(/sk-[a-zA-Z0-9]+/g, '[REDACTED]')
82
+ .replace(
83
+ /\b(?:api[_-]?key|password|token|secret)\s*[:=]\s*\S+/gi,
84
+ '[REDACTED]',
85
+ )
86
+ .replace(
87
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
88
+ '[EMAIL_REDACTED]',
89
+ )
90
+ .replace(/\b\d{4}\s?\d{4}\s?\d{4}\s?\d{4}\b/g, '[CARD_REDACTED]');
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Legacy sanitizer for backward compatibility
96
+ */
97
+ function sanitizeOutput(obj) {
98
+ const clone = JSON.parse(JSON.stringify(obj));
99
+ const redact = (s) =>
100
+ String(s)
101
+ .replace(/sk-[A-Za-z0-9_-]{10,}/g, '[REDACTED]')
102
+ .replace(/\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g, '[EMAIL_REDACTED]')
103
+ .replace(/\b(?:\d[ -]*?){13,16}\b/g, '[REDACTED]');
104
+ if (typeof clone === 'string') return redact(clone);
105
+ if (clone && typeof clone === 'object') {
106
+ for (const k of Object.keys(clone)) {
107
+ if (typeof clone[k] === 'string') clone[k] = redact(clone[k]);
108
+ }
109
+ }
110
+ return clone;
111
+ }
112
+
113
+ /**
114
+ * Secure plugin sandbox for executing third-party plugins with strict isolation.
115
+ *
116
+ * Features:
117
+ * - VM-based execution isolation
118
+ * - Resource limits (memory, CPU time, I/O)
119
+ * - Permission-based access control
120
+ * - Audit logging for security compliance
121
+ * - Plugin signature verification
122
+ * - Fail-closed security model
123
+ *
124
+ * @class PluginSandbox
125
+ * @extends EventEmitter
126
+ */
127
+ class PluginSandbox extends EventEmitter {
128
+ constructor(options = {}) {
129
+ super();
130
+
131
+ this.config = {
132
+ // VM Configuration
133
+ timeout: options.timeout || 30000, // 30 seconds max execution
134
+ memoryLimit: options.memoryLimit || 128 * 1024 * 1024, // 128MB
135
+
136
+ // Security Configuration
137
+ allowEval: false, // Never allow eval in sandbox
138
+ allowWasm: false, // Block WebAssembly
139
+ allowAsync: options.allowAsync || true,
140
+
141
+ // Permission System
142
+ permissions: {
143
+ filesystem: options.permissions?.filesystem || 'none', // none, read, write
144
+ network: options.permissions?.network || 'none', // none, limited, full
145
+ process: options.permissions?.process || 'none', // none, limited
146
+ crypto: options.permissions?.crypto || 'limited', // none, limited, full
147
+ ...options.permissions,
148
+ },
149
+
150
+ // Resource Limits
151
+ maxFileSize: options.maxFileSize || 10 * 1024 * 1024, // 10MB
152
+ maxNetworkRequests: options.maxNetworkRequests || 10,
153
+ maxCpuTime: options.maxCpuTime || 5000, // 5 seconds CPU time
154
+
155
+ // Audit Configuration
156
+ auditEnabled: options.auditEnabled !== false,
157
+ logLevel: options.logLevel || 'info',
158
+
159
+ ...options,
160
+ };
161
+
162
+ this.signatureVerifier = new PluginSignatureVerifier();
163
+ this.activeSandboxes = new Map();
164
+ this.executionMetrics = new Map();
165
+ }
166
+
167
+ /**
168
+ * Execute a plugin within the secure sandbox environment.
169
+ *
170
+ * @param {Object} plugin - Plugin configuration and code
171
+ * @param {Object} context - Execution context and input data
172
+ * @param {Object} options - Override options for this execution
173
+ * @returns {Promise<any>} Plugin execution result
174
+ * @throws {SecurityError} On security policy violations
175
+ * @throws {ResourceError} On resource limit exceeded
176
+ * @throws {ValidationError} On plugin validation failure
177
+ */
178
+ async execute(plugin, context = {}, options = {}) {
179
+ const executionId = crypto.randomUUID();
180
+ const startTime = Date.now();
181
+
182
+ return logger.withCorrelation(executionId, async () => {
183
+ this.auditLog('sandbox.execution.start', {
184
+ executionId,
185
+ pluginId: plugin.id,
186
+ permissions: this.config.permissions,
187
+ resourceLimits: {
188
+ timeout: this.config.timeout,
189
+ memoryLimit: this.config.memoryLimit,
190
+ maxCpuTime: this.config.maxCpuTime,
191
+ },
192
+ });
193
+
194
+ try {
195
+ // 1. Validate and verify plugin
196
+ await this.validatePlugin(plugin);
197
+
198
+ // 2. Create isolated execution environment
199
+ const sandbox = this.createSandbox(plugin, context, executionId);
200
+
201
+ // 3. Execute with monitoring
202
+ const result = await this.executeInSandbox(
203
+ sandbox,
204
+ plugin,
205
+ context,
206
+ options,
207
+ );
208
+
209
+ // 4. Record success metrics
210
+ this.recordMetrics(executionId, startTime, 'success', result);
211
+
212
+ this.auditLog('sandbox.execution.success', {
213
+ executionId,
214
+ pluginId: plugin.id,
215
+ duration: Date.now() - startTime,
216
+ resultSize: JSON.stringify(result).length,
217
+ });
218
+
219
+ return result;
220
+ } catch (error) {
221
+ this.recordMetrics(executionId, startTime, 'error', null, error);
222
+
223
+ this.auditLog('sandbox.execution.error', {
224
+ executionId,
225
+ pluginId: plugin.id,
226
+ error: error.message,
227
+ errorType: error.constructor.name,
228
+ duration: Date.now() - startTime,
229
+ });
230
+
231
+ // Clean up any resources
232
+ this.cleanupSandbox(executionId);
233
+
234
+ throw error;
235
+ }
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Enforce rate limiting
241
+ */
242
+ _enforceRateLimit(customRateLimit = {}) {
243
+ const rateLimit = { ...this.options.rateLimit, ...customRateLimit };
244
+ const key = rateLimit.key || 'default';
245
+ const now = Date.now();
246
+
247
+ const entry = this.rateLimitStore.get(key) || {
248
+ count: 0,
249
+ resetAt: now + rateLimit.windowMs,
250
+ };
251
+
252
+ if (now > entry.resetAt) {
253
+ entry.count = 0;
254
+ entry.resetAt = now + rateLimit.windowMs;
255
+ }
256
+
257
+ entry.count += 1;
258
+ this.rateLimitStore.set(key, entry);
259
+
260
+ if (entry.count > rateLimit.max) {
261
+ throw new Error(
262
+ `Rate limit exceeded: ${entry.count}/${rateLimit.max} requests`,
263
+ );
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Validate plugin structure and security
269
+ */
270
+ _validatePlugin(plugin) {
271
+ if (!plugin || typeof plugin !== 'object') {
272
+ throw new Error('Invalid plugin: must be an object');
273
+ }
274
+
275
+ if (typeof plugin.generate !== 'function') {
276
+ throw new Error('Invalid plugin: generate method is required');
277
+ }
278
+
279
+ // Check for suspicious properties
280
+ const suspiciousProps = ['__proto__', 'constructor', 'prototype'];
281
+ for (const prop of suspiciousProps) {
282
+ if (plugin.hasOwnProperty(prop)) {
283
+ throw new Error(
284
+ `Invalid plugin: suspicious property '${prop}' detected`,
285
+ );
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Execute plugin in isolated VM context using isolated-vm
292
+ */
293
+ async _executeInSandbox(plugin, prompt, executionId) {
294
+ let isolate;
295
+ let context;
296
+
297
+ try {
298
+ // Create new isolate with memory limit
299
+ isolate = new ivm.Isolate({
300
+ memoryLimit: Math.floor(this.options.memoryLimit / (1024 * 1024)), // Convert to MB
301
+ });
302
+
303
+ // Create context
304
+ context = await isolate.createContext();
305
+
306
+ // Set up global environment
307
+ const jail = context.global;
308
+ await jail.set('global', jail.derefInto());
309
+
310
+ // Create secure execution environment
311
+ const secureGlobals = this._createSecureGlobals(executionId);
312
+ for (const [key, value] of Object.entries(secureGlobals)) {
313
+ if (typeof value === 'function') {
314
+ await jail.set(key, new ivm.Reference(value));
315
+ } else {
316
+ await jail.set(key, value);
317
+ }
318
+ }
319
+
320
+ // Inject plugin and prompt safely
321
+ await jail.set(
322
+ 'pluginCode',
323
+ new ivm.ExternalCopy(plugin.generate.toString()).copyInto(),
324
+ );
325
+ await jail.set('promptData', new ivm.ExternalCopy(prompt).copyInto());
326
+ await jail.set('memoryLimit', this.options.memoryLimit);
327
+
328
+ // Create wrapper function for secure execution
329
+ const wrapperCode = `
330
+ (async function executePlugin() {
331
+ try {
332
+ // Reconstruct plugin function
333
+ const pluginFunc = eval('(' + pluginCode + ')');
334
+
335
+ // Execute with timeout protection
336
+ const result = await pluginFunc(promptData);
337
+
338
+ return result;
339
+ } catch (error) {
340
+ throw new Error('Plugin execution failed: ' + error.message);
341
+ }
342
+ })();
343
+ `;
344
+
345
+ // Execute with timeout
346
+ const result = await context.eval(wrapperCode, {
347
+ timeout: this.options.timeoutMs,
348
+ promise: true,
349
+ });
350
+
351
+ return result;
352
+ } catch (error) {
353
+ throw new Error(`Isolated VM execution failed: ${error.message}`);
354
+ } finally {
355
+ // Clean up resources
356
+ if (context) {
357
+ context.release();
358
+ }
359
+ if (isolate) {
360
+ isolate.dispose();
361
+ }
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Create secure globals for isolated-vm
367
+ */
368
+ _createSecureGlobals(executionId) {
369
+ return {
370
+ // Safe console methods (silenced for security)
371
+ console: {
372
+ log: () => {},
373
+ error: () => {},
374
+ warn: () => {},
375
+ info: () => {},
376
+ },
377
+
378
+ // Limited timing functions
379
+ setTimeout: (fn, delay) => {
380
+ if (delay > 5000) throw new Error('Timeout delay too long');
381
+ return setTimeout(fn, delay);
382
+ },
383
+ clearTimeout,
384
+
385
+ // Safe utilities
386
+ Math,
387
+ JSON,
388
+ Date,
389
+
390
+ // Execution context identifier
391
+ __SANDBOX_ID__: executionId,
392
+ };
393
+ }
394
+
395
+ /**
396
+ * Create secure sandbox context (legacy method for compatibility)
397
+ */
398
+ _createSandboxContext(executionId) {
399
+ const sandbox = {
400
+ // Safe console methods
401
+ console: {
402
+ log: () => {}, // Silenced for security
403
+ error: () => {},
404
+ warn: () => {},
405
+ info: () => {},
406
+ },
407
+
408
+ // Limited timing functions
409
+ setTimeout: (fn, delay) => {
410
+ if (delay > 5000) throw new Error('Timeout delay too long');
411
+ return setTimeout(fn, delay);
412
+ },
413
+ clearTimeout,
414
+ setImmediate,
415
+
416
+ // Restricted process object
417
+ process: {
418
+ env: {}, // Empty environment
419
+ memoryUsage: process.memoryUsage.bind(process),
420
+ hrtime: process.hrtime.bind(process),
421
+ },
422
+
423
+ // Safe Buffer access
424
+ Buffer: {
425
+ from: Buffer.from.bind(Buffer),
426
+ alloc: (size) => {
427
+ if (size > 1024 * 1024) throw new Error('Buffer size too large');
428
+ return Buffer.alloc(size);
429
+ },
430
+ },
431
+
432
+ // Math and JSON are safe
433
+ Math,
434
+ JSON,
435
+ Date,
436
+
437
+ // Block dangerous globals
438
+ require: () => {
439
+ throw new Error('require() is not allowed in sandbox');
440
+ },
441
+ import: () => {
442
+ throw new Error('import() is not allowed in sandbox');
443
+ },
444
+ eval: () => {
445
+ throw new Error('eval() is not allowed in sandbox');
446
+ },
447
+ Function: () => {
448
+ throw new Error('Function() constructor is not allowed in sandbox');
449
+ },
450
+
451
+ // Network restrictions
452
+ fetch: this.options.enableNetworking
453
+ ? fetch
454
+ : () => {
455
+ throw new Error('Network access is not allowed in sandbox');
456
+ },
457
+
458
+ // File system restrictions
459
+ fs: this.options.enableFileSystem ? require('fs') : undefined,
460
+
461
+ // Global object restrictions
462
+ global: undefined,
463
+ globalThis: undefined,
464
+ window: undefined,
465
+
466
+ // Execution context identifier
467
+ __SANDBOX_ID__: executionId,
468
+ };
469
+
470
+ return sandbox;
471
+ }
472
+
473
+ /**
474
+ * Sanitize execution result
475
+ */
476
+ _sanitizeResult(result) {
477
+ try {
478
+ // Use enhanced output sanitizer
479
+ if (typeof result === 'object' && result !== null) {
480
+ return OutputSanitizer.sanitizeOutput(result);
481
+ }
482
+
483
+ // For string results, use text sanitization
484
+ if (typeof result === 'string') {
485
+ return OutputSanitizer.sanitizeText(result);
486
+ }
487
+
488
+ // For primitive types, return as-is but ensure no sensitive data
489
+ return sanitizeOutput(result);
490
+ } catch (error) {
491
+ return { error: 'Result sanitization failed', message: error.message };
492
+ }
493
+ }
494
+
495
+ /**
496
+ * Get sandbox statistics
497
+ */
498
+ getStats() {
499
+ return {
500
+ activeExecutions: this.activeExecutions.size,
501
+ rateLimitEntries: this.rateLimitStore.size,
502
+ options: this.options,
503
+ };
504
+ }
505
+
506
+ /**
507
+ * Clean up rate limit entries
508
+ */
509
+ cleanup() {
510
+ const now = Date.now();
511
+ for (const [key, entry] of this.rateLimitStore) {
512
+ if (now > entry.resetAt) {
513
+ this.rateLimitStore.delete(key);
514
+ }
515
+ }
516
+ }
517
+ }
518
+
519
+ /**
520
+ * Simple sandbox runner with timeout and abort support
521
+ * @param {Function} fn - Function to execute
522
+ * @param {Object} options - Options object
523
+ * @param {number} options.timeoutMs - Timeout in milliseconds (default: 5000)
524
+ * @param {AbortSignal} options.signal - AbortSignal for cancellation
525
+ * @returns {Promise<Object>} Result object with success/error
526
+ */
527
+ async function run(fn, { timeoutMs = 5000, signal } = {}) {
528
+ return new Promise((resolve) => {
529
+ let isResolved = false;
530
+
531
+ const resolveOnce = (result) => {
532
+ if (!isResolved) {
533
+ isResolved = true;
534
+ resolve(result);
535
+ }
536
+ };
537
+
538
+ // Handle abort signal
539
+ if (signal && signal.aborted) {
540
+ return resolveOnce({ success: false, error: 'aborted' });
541
+ }
542
+
543
+ let abortListener;
544
+ if (signal) {
545
+ abortListener = () => {
546
+ resolveOnce({ success: false, error: 'aborted' });
547
+ };
548
+ signal.addEventListener('abort', abortListener);
549
+ }
550
+
551
+ // Set up timeout
552
+ const timeoutId = setTimeout(() => {
553
+ resolveOnce({ success: false, error: 'timeout' });
554
+ }, timeoutMs);
555
+
556
+ // Execute function
557
+ Promise.resolve()
558
+ .then(() => fn())
559
+ .then((result) => {
560
+ clearTimeout(timeoutId);
561
+ if (signal && abortListener) {
562
+ signal.removeEventListener('abort', abortListener);
563
+ }
564
+ resolveOnce({ success: true, result });
565
+ })
566
+ .catch((error) => {
567
+ clearTimeout(timeoutId);
568
+ if (signal && abortListener) {
569
+ signal.removeEventListener('abort', abortListener);
570
+ }
571
+ const errorMessage = String(error.message || error);
572
+ resolveOnce({ success: false, error: errorMessage });
573
+ });
574
+ });
575
+ }
576
+
577
+ module.exports = {
578
+ PluginSandbox,
579
+ sanitizeOutput,
580
+ InputSanitizer,
581
+ OutputSanitizer,
582
+ run,
583
+ };
584
+
585
+ // CommonJS default export compatibility
586
+ module.exports.default = module.exports;