@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.
- package/.ragrc.schema.json +218 -0
- package/README.md +178 -550
- package/bin/cli.js +8 -14
- package/contracts/reranker-contract.json +69 -0
- package/dist/index.d.ts +237 -0
- package/dist/index.mjs +22 -7
- package/package.json +18 -13
- package/src/ai/adaptive-retrieval.js +191 -0
- package/src/ai/federated-learning.js +214 -0
- package/src/ai/federation/federated-learning-coordinator.js +345 -0
- package/src/ai/federation.js +76 -0
- package/src/ai/index-fixed.js +509 -0
- package/src/ai/index.js +33 -0
- package/src/ai/model-trainer.js +217 -0
- package/src/ai/model-training.js +205 -0
- package/src/ai/module-template.js +37 -0
- package/src/ai/multimodal/multi-modal-processor.js +345 -0
- package/src/ai/multimodal-processing.js +268 -0
- package/src/ai/multimodal.js +71 -0
- package/src/ai/orchestrator.js +70 -0
- package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
- package/src/ai/retrieval-engine.js +69 -0
- package/src/ai/training/model-training-orchestrator.js +228 -0
- package/src/ci/workflow-analyzer.js +71 -0
- package/src/cli/commands/ai-ml.js +967 -0
- package/src/cli/commands/docs.js +228 -0
- package/src/cli/commands/dx.js +538 -0
- package/src/cli/commands/plugin-hub.js +875 -0
- package/src/cli/doctor-command.js +639 -0
- package/src/cli/enhanced-cli-commands.js +945 -0
- package/src/cli/interactive-wizard.js +997 -0
- package/src/cli/plugin-marketplace-commands.js +869 -0
- package/src/cli.js +17 -0
- package/src/config/enhanced-ragrc-schema.js +632 -0
- package/src/config/load-config.js +54 -0
- package/src/config/load-plugin-config.js +69 -0
- package/src/config/normalize-config.js +105 -0
- package/src/config/schema-validator.js +315 -0
- package/src/config/validate-plugin-schema.js +35 -0
- package/src/config/validate-schema.js +113 -0
- package/src/contracts/plugin-contracts.js +33 -0
- package/src/core/create-pipeline.js +99 -0
- package/src/core/dependency-injection.js +242 -0
- package/src/core/observability/event-logger.js +241 -0
- package/src/core/observability/instrumented-pipeline.js +609 -0
- package/src/core/observability/metrics.js +497 -0
- package/src/core/observability/tracing.js +781 -0
- package/src/core/performance/benchmark.js +516 -0
- package/src/core/performance/parallel-processor.js +240 -0
- package/src/core/performance/streaming-safeguards.js +620 -0
- package/src/core/pipeline-factory.js +60 -0
- package/src/core/plugin-contracts.js +89 -0
- package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
- package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
- package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
- package/src/core/plugin-marketplace/version-resolver.js +61 -0
- package/src/core/plugin-registry.js +294 -0
- package/src/dag/dag-engine.js +954 -0
- package/src/dag/dag-node.js +76 -0
- package/src/dag/enhanced-dag-engine.js +822 -0
- package/src/dx/index.js +40 -0
- package/src/dx/integration-templates.js +665 -0
- package/src/dx/performance-profiler.js +968 -0
- package/src/dx/realtime-debugger.js +755 -0
- package/src/dx/visual-pipeline-builder.js +578 -0
- package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
- package/src/ecosystem/plugin-certification.js +580 -0
- package/src/ecosystem/plugin-hub.js +821 -0
- package/src/enterprise/audit-logging.js +967 -0
- package/src/enterprise/data-governance.js +408 -0
- package/src/enterprise/multi-tenancy.js +496 -0
- package/src/enterprise/sso-integration.js +1030 -0
- package/src/evaluate/evaluator.js +52 -0
- package/src/evaluate/scoring.js +91 -0
- package/src/index.js +67 -0
- package/src/ingest.js +59 -0
- package/src/loader/csv-loader.js +38 -0
- package/src/loader/directory-loader.js +48 -0
- package/src/loader/html-loader.js +62 -0
- package/src/loader/markdown-loader.js +67 -0
- package/src/mocks/openai-embedder.js +119 -0
- package/src/mocks/openai-llm.js +77 -0
- package/src/mocks/pdf-loader.js +21 -0
- package/src/mocks/pinecone-retriever.js +106 -0
- package/src/observability/audit-logger.js +758 -0
- package/src/observability/dashboard-config.json +675 -0
- package/src/observability/event-logger.js +310 -0
- package/src/observability/metrics.js +691 -0
- package/src/observability/security-monitor.js +830 -0
- package/src/observability/slo-monitor.js +319 -0
- package/src/observability/telemetry.js +686 -0
- package/src/pipeline/pipeline.js +18 -0
- package/src/plugins/sample-embedder.js +53 -0
- package/src/plugins/sample-llm.js +30 -0
- package/src/plugins/sample-loader.js +24 -0
- package/src/plugins/sample-retriever.js +84 -0
- package/src/query.js +117 -0
- package/src/reranker/llm-reranker.js +111 -0
- package/src/scripts/script-utilities.js +36 -0
- package/src/security/plugin-sandbox.js +586 -0
- package/src/security/plugin-signature-verifier.js +414 -0
- package/src/security/validator.js +36 -0
- package/src/utils/ci/diagnostic-reporter.js +57 -0
- package/src/utils/logger.js +211 -0
- package/src/utils/performance-monitor.js +561 -0
- package/src/utils/plugin-scaffolder.js +447 -0
- package/src/utils/rate-limiter.js +244 -0
- package/src/utils/retry-policy.js +443 -0
- package/src/utils/retry.js +107 -0
- package/src/utils/safe-expression-evaluator.js +579 -0
- package/src/utils/secure-logging.js +338 -0
- package/src/utils/structured-logger.js +388 -0
- 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;
|