@unrdf/hooks 5.0.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +86 -0
  3. package/package.json +70 -0
  4. package/src/hooks/builtin-hooks.mjs +296 -0
  5. package/src/hooks/condition-cache.mjs +109 -0
  6. package/src/hooks/condition-evaluator.mjs +722 -0
  7. package/src/hooks/define-hook.mjs +211 -0
  8. package/src/hooks/effect-sandbox-worker.mjs +170 -0
  9. package/src/hooks/effect-sandbox.mjs +517 -0
  10. package/src/hooks/file-resolver.mjs +387 -0
  11. package/src/hooks/hook-chain-compiler.mjs +236 -0
  12. package/src/hooks/hook-executor-batching.mjs +277 -0
  13. package/src/hooks/hook-executor.mjs +465 -0
  14. package/src/hooks/hook-management.mjs +202 -0
  15. package/src/hooks/hook-scheduler.mjs +413 -0
  16. package/src/hooks/knowledge-hook-engine.mjs +358 -0
  17. package/src/hooks/knowledge-hook-manager.mjs +269 -0
  18. package/src/hooks/observability.mjs +531 -0
  19. package/src/hooks/policy-pack.mjs +572 -0
  20. package/src/hooks/quad-pool.mjs +249 -0
  21. package/src/hooks/quality-metrics.mjs +544 -0
  22. package/src/hooks/security/error-sanitizer.mjs +257 -0
  23. package/src/hooks/security/path-validator.mjs +194 -0
  24. package/src/hooks/security/sandbox-restrictions.mjs +331 -0
  25. package/src/hooks/telemetry.mjs +167 -0
  26. package/src/index.mjs +101 -0
  27. package/src/security/sandbox/browser-executor.mjs +220 -0
  28. package/src/security/sandbox/detector.mjs +342 -0
  29. package/src/security/sandbox/isolated-vm-executor.mjs +373 -0
  30. package/src/security/sandbox/vm2-executor.mjs +217 -0
  31. package/src/security/sandbox/worker-executor-runtime.mjs +74 -0
  32. package/src/security/sandbox/worker-executor.mjs +212 -0
  33. package/src/security/sandbox-adapter.mjs +141 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @file Browser Executor
3
+ * @module security/sandbox/browser-executor
4
+ *
5
+ * @description
6
+ * Browser-based sandbox executor using Web Workers.
7
+ * Provides sandboxed execution in browser environments with:
8
+ * - Separate worker context
9
+ * - Message-based communication
10
+ * - WASM support
11
+ * - Async/await support
12
+ */
13
+
14
+ /* global Worker */
15
+ import { trace } from '@opentelemetry/api';
16
+
17
+ const tracer = trace.getTracer('browser-executor');
18
+
19
+ /**
20
+ * Browser Executor (Web Workers)
21
+ */
22
+ export class BrowserExecutor {
23
+ /**
24
+ * @param {Object} [config] - Executor configuration
25
+ * @param {number} [config.timeout=5000] - Execution timeout in ms
26
+ * @param {boolean} [config.enableWasm=true] - Enable WASM support
27
+ * @param {boolean} [config.strictMode=true] - Enable strict mode
28
+ */
29
+ constructor(config = {}) {
30
+ this.config = {
31
+ timeout: config.timeout || 5000,
32
+ enableWasm: config.enableWasm !== false,
33
+ strictMode: config.strictMode !== false,
34
+ ...config,
35
+ };
36
+
37
+ /** @type {Map<string, Worker>} */
38
+ this.workers = new Map();
39
+
40
+ this.executionCount = 0;
41
+ this.totalDuration = 0;
42
+ }
43
+
44
+ /**
45
+ * Execute code in Web Worker
46
+ * @param {string|Function} code - Code to execute
47
+ * @param {Object} [context] - Execution context
48
+ * @param {Object} [options] - Execution options
49
+ * @returns {Promise<Object>} Execution result
50
+ */
51
+ async run(code, context = {}, options = {}) {
52
+ return tracer.startActiveSpan('security.browser.execute', async span => {
53
+ const startTime = Date.now();
54
+ const executionId = `exec_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
55
+
56
+ try {
57
+ span.setAttributes({
58
+ 'security.executor.type': 'browser',
59
+ 'security.execution.id': executionId,
60
+ 'security.timeout': options.timeout || this.config.timeout,
61
+ });
62
+
63
+ // Convert function to string if needed
64
+ const codeString = typeof code === 'function' ? code.toString() : code;
65
+
66
+ // Wrap code in strict mode if enabled
67
+ const wrappedCode = this.config.strictMode ? `"use strict";\n${codeString}` : codeString;
68
+
69
+ // Create worker blob
70
+ const workerCode = `
71
+ self.onmessage = function(e) {
72
+ const { code, context } = e.data;
73
+
74
+ try {
75
+ // Create safe environment
76
+ const sandbox = {
77
+ console: {
78
+ log: (...args) => self.postMessage({ type: 'log', args }),
79
+ error: (...args) => self.postMessage({ type: 'error', args }),
80
+ warn: (...args) => self.postMessage({ type: 'warn', args }),
81
+ info: (...args) => self.postMessage({ type: 'info', args })
82
+ },
83
+ Date: { now: () => Date.now() },
84
+ Math: Math,
85
+ JSON: JSON,
86
+ ...context
87
+ };
88
+
89
+ // Execute code
90
+ const func = new Function(...Object.keys(sandbox), code);
91
+ const result = func(...Object.values(sandbox));
92
+
93
+ self.postMessage({ type: 'result', success: true, result });
94
+ } catch (error) {
95
+ self.postMessage({
96
+ type: 'result',
97
+ success: false,
98
+ error: error.message,
99
+ stack: error.stack
100
+ });
101
+ }
102
+ };
103
+ `;
104
+
105
+ const blob = new Blob([workerCode], { type: 'application/javascript' });
106
+ const workerUrl = URL.createObjectURL(blob);
107
+
108
+ // Create worker promise
109
+ const result = await new Promise((resolve, reject) => {
110
+ const timeout = setTimeout(() => {
111
+ worker.terminate();
112
+ this.workers.delete(executionId);
113
+ URL.revokeObjectURL(workerUrl);
114
+ reject(new Error(`Browser worker execution timeout after ${this.config.timeout}ms`));
115
+ }, options.timeout || this.config.timeout);
116
+
117
+ const worker = new Worker(workerUrl);
118
+ this.workers.set(executionId, worker);
119
+
120
+ worker.onmessage = e => {
121
+ const { type, success, result, error, args } = e.data;
122
+
123
+ if (type === 'log' || type === 'error' || type === 'warn' || type === 'info') {
124
+ console[type]('[Worker]', ...args);
125
+ return;
126
+ }
127
+
128
+ if (type === 'result') {
129
+ clearTimeout(timeout);
130
+ worker.terminate();
131
+ this.workers.delete(executionId);
132
+ URL.revokeObjectURL(workerUrl);
133
+
134
+ if (success) {
135
+ resolve({ success: true, result });
136
+ } else {
137
+ reject(new Error(error || 'Worker execution failed'));
138
+ }
139
+ }
140
+ };
141
+
142
+ worker.onerror = error => {
143
+ clearTimeout(timeout);
144
+ worker.terminate();
145
+ this.workers.delete(executionId);
146
+ URL.revokeObjectURL(workerUrl);
147
+ reject(error);
148
+ };
149
+
150
+ // Send code to worker
151
+ worker.postMessage({ code: wrappedCode, context });
152
+ });
153
+
154
+ const duration = Date.now() - startTime;
155
+ this.executionCount++;
156
+ this.totalDuration += duration;
157
+
158
+ span.setAttributes({
159
+ 'security.execution.duration': duration,
160
+ 'security.execution.success': true,
161
+ });
162
+ span.setStatus({ code: 1 }); // OK
163
+
164
+ return {
165
+ success: true,
166
+ result: result.result,
167
+ duration,
168
+ executionId,
169
+ };
170
+ } catch (error) {
171
+ const duration = Date.now() - startTime;
172
+
173
+ span.recordException(error);
174
+ span.setAttributes({
175
+ 'security.execution.duration': duration,
176
+ 'security.execution.success': false,
177
+ 'security.error.message': error.message,
178
+ });
179
+ span.setStatus({ code: 2, message: error.message });
180
+
181
+ return {
182
+ success: false,
183
+ error: error.message,
184
+ duration,
185
+ executionId,
186
+ };
187
+ } finally {
188
+ span.end();
189
+ }
190
+ });
191
+ }
192
+
193
+ /**
194
+ * Get executor statistics
195
+ * @returns {Object}
196
+ */
197
+ getStats() {
198
+ return {
199
+ type: 'browser',
200
+ config: this.config,
201
+ executionCount: this.executionCount,
202
+ averageDuration: this.executionCount > 0 ? this.totalDuration / this.executionCount : 0,
203
+ activeWorkers: this.workers.size,
204
+ };
205
+ }
206
+
207
+ /**
208
+ * Cleanup all workers
209
+ */
210
+ async cleanup() {
211
+ for (const [executionId, worker] of this.workers.entries()) {
212
+ try {
213
+ worker.terminate();
214
+ } catch (err) {
215
+ // Ignore termination errors
216
+ }
217
+ this.workers.delete(executionId);
218
+ }
219
+ }
220
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * @file Sandbox Executor Detector
3
+ * @module security/sandbox/detector
4
+ *
5
+ * @description
6
+ * Automatically detects the best sandbox executor based on:
7
+ * - Node.js version and environment
8
+ * - Available dependencies
9
+ * - Platform capabilities (V8 isolates, Worker threads, etc.)
10
+ * - Security requirements
11
+ */
12
+
13
+ import { trace } from '@opentelemetry/api';
14
+
15
+ const tracer = trace.getTracer('sandbox-detector');
16
+
17
+ /**
18
+ * Detect runtime environment
19
+ * @returns {Object} Environment info
20
+ */
21
+ export function detectEnvironment() {
22
+ return tracer.startActiveSpan('detector.detectEnvironment', span => {
23
+ try {
24
+ const env = {
25
+ isNode: typeof process !== 'undefined' && process.versions?.node,
26
+ isBrowser: typeof window !== 'undefined',
27
+ isWorker: typeof WorkerGlobalScope !== 'undefined',
28
+ nodeVersion: process.versions?.node || null,
29
+ v8Version: process.versions?.v8 || null,
30
+ platform: process.platform || 'unknown',
31
+ arch: process.arch || 'unknown',
32
+ };
33
+
34
+ span.setAttributes({
35
+ 'detector.isNode': env.isNode,
36
+ 'detector.isBrowser': env.isBrowser,
37
+ 'detector.nodeVersion': env.nodeVersion || 'unknown',
38
+ 'detector.platform': env.platform,
39
+ });
40
+
41
+ span.setStatus({ code: 1 }); // OK
42
+ return env;
43
+ } catch (error) {
44
+ span.recordException(error);
45
+ span.setStatus({ code: 2, message: error.message });
46
+ throw error;
47
+ } finally {
48
+ span.end();
49
+ }
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Check if isolated-vm is available and working
55
+ * @returns {Promise<boolean>} True if isolated-vm is available
56
+ */
57
+ export async function checkIsolatedVm() {
58
+ return tracer.startActiveSpan('detector.checkIsolatedVm', async span => {
59
+ try {
60
+ // Try to import isolated-vm
61
+ const ivm = await import('isolated-vm').catch(() => null);
62
+ const available = !!ivm;
63
+
64
+ span.setAttribute('detector.isolatedVm.available', available);
65
+
66
+ if (available) {
67
+ // Quick smoke test
68
+ try {
69
+ const isolate = new ivm.default.Isolate({ memoryLimit: 8 });
70
+ await isolate.dispose();
71
+ span.setAttribute('detector.isolatedVm.working', true);
72
+ span.setStatus({ code: 1 });
73
+ return true;
74
+ } catch (testError) {
75
+ span.recordException(testError);
76
+ span.setAttribute('detector.isolatedVm.working', false);
77
+ span.setStatus({ code: 1 }); // Not an error, just not working
78
+ return false;
79
+ }
80
+ }
81
+
82
+ span.setStatus({ code: 1 });
83
+ return false;
84
+ } catch (error) {
85
+ span.recordException(error);
86
+ span.setStatus({ code: 2, message: error.message });
87
+ return false;
88
+ } finally {
89
+ span.end();
90
+ }
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Check if Worker threads are available
96
+ * @returns {Promise<boolean>} True if Worker threads are available
97
+ */
98
+ export async function checkWorkerThreads() {
99
+ return tracer.startActiveSpan('detector.checkWorkerThreads', async span => {
100
+ try {
101
+ const { Worker } = await import('worker_threads').catch(() => ({}));
102
+ const available = !!Worker;
103
+
104
+ span.setAttribute('detector.workerThreads.available', available);
105
+ span.setStatus({ code: 1 });
106
+
107
+ return available;
108
+ } catch (error) {
109
+ span.recordException(error);
110
+ span.setStatus({ code: 2, message: error.message });
111
+ return false;
112
+ } finally {
113
+ span.end();
114
+ }
115
+ });
116
+ }
117
+
118
+ /**
119
+ * Check if vm2 is available (legacy)
120
+ * @returns {Promise<boolean>} True if vm2 is available
121
+ */
122
+ export async function checkVm2() {
123
+ return tracer.startActiveSpan('detector.checkVm2', async span => {
124
+ try {
125
+ const vm2Module = await import('vm2').catch(() => null);
126
+ const available = !!vm2Module?.VM;
127
+
128
+ span.setAttribute('detector.vm2.available', available);
129
+ span.setAttribute('detector.vm2.deprecated', true);
130
+ span.setStatus({ code: 1 });
131
+
132
+ return available;
133
+ } catch (error) {
134
+ span.recordException(error);
135
+ span.setStatus({ code: 2, message: error.message });
136
+ return false;
137
+ } finally {
138
+ span.end();
139
+ }
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Detect best available sandbox executor
145
+ * Priority: isolated-vm > worker > vm2 (deprecated) > browser
146
+ *
147
+ * @param {Object} [options] - Detection options
148
+ * @param {boolean} [options.preferIsolatedVm=true] - Prefer isolated-vm if available
149
+ * @param {boolean} [options.allowVm2=false] - Allow deprecated vm2 (not recommended)
150
+ * @param {boolean} [options.allowBrowser=true] - Allow browser executor
151
+ * @returns {Promise<string>} Executor type ('isolated-vm' | 'worker' | 'vm2' | 'browser')
152
+ */
153
+ export async function detectBestExecutor(options = {}) {
154
+ return tracer.startActiveSpan('detector.detectBestExecutor', async span => {
155
+ try {
156
+ const { preferIsolatedVm = true, allowVm2 = false, allowBrowser = true } = options;
157
+
158
+ const env = detectEnvironment();
159
+ span.setAttributes({
160
+ 'detector.preferIsolatedVm': preferIsolatedVm,
161
+ 'detector.allowVm2': allowVm2,
162
+ 'detector.allowBrowser': allowBrowser,
163
+ });
164
+
165
+ // Browser environment
166
+ if (env.isBrowser && allowBrowser) {
167
+ span.setAttribute('detector.executor', 'browser');
168
+ span.setStatus({ code: 1 });
169
+ return 'browser';
170
+ }
171
+
172
+ // Node.js environment - check capabilities in priority order
173
+ if (env.isNode) {
174
+ // 1. Try isolated-vm (best security and performance)
175
+ if (preferIsolatedVm && (await checkIsolatedVm())) {
176
+ span.setAttribute('detector.executor', 'isolated-vm');
177
+ span.setStatus({ code: 1 });
178
+ return 'isolated-vm';
179
+ }
180
+
181
+ // 2. Try Worker threads (good isolation, available in Node 12+)
182
+ if (await checkWorkerThreads()) {
183
+ span.setAttribute('detector.executor', 'worker');
184
+ span.setStatus({ code: 1 });
185
+ return 'worker';
186
+ }
187
+
188
+ // 3. Fall back to vm2 if explicitly allowed (deprecated, security issues)
189
+ if (allowVm2 && (await checkVm2())) {
190
+ span.setAttribute('detector.executor', 'vm2');
191
+ span.setAttribute('detector.warning', 'Using deprecated vm2 - security issues present');
192
+ span.setStatus({ code: 1 });
193
+ console.warn(
194
+ '[SECURITY WARNING] Using deprecated vm2 sandbox executor. ' +
195
+ 'This has known security vulnerabilities. ' +
196
+ 'Consider upgrading Node.js or installing isolated-vm.'
197
+ );
198
+ return 'vm2';
199
+ }
200
+ }
201
+
202
+ // No suitable executor found
203
+ const error = new Error(
204
+ 'No suitable sandbox executor available. ' +
205
+ 'Install isolated-vm or upgrade to Node.js 12+ for Worker threads.'
206
+ );
207
+ span.recordException(error);
208
+ span.setStatus({ code: 2, message: error.message });
209
+ throw error;
210
+ } catch (error) {
211
+ span.recordException(error);
212
+ span.setStatus({ code: 2, message: error.message });
213
+ throw error;
214
+ } finally {
215
+ span.end();
216
+ }
217
+ });
218
+ }
219
+
220
+ /**
221
+ * Create executor instance based on type
222
+ * @param {string} executorType - Type of executor to create
223
+ * @param {Object} [config] - Executor configuration
224
+ * @returns {Promise<Object>} Executor instance
225
+ */
226
+ export async function createExecutor(executorType, config = {}) {
227
+ return tracer.startActiveSpan('detector.createExecutor', async span => {
228
+ try {
229
+ span.setAttribute('detector.executorType', executorType);
230
+
231
+ let ExecutorClass;
232
+
233
+ switch (executorType) {
234
+ case 'isolated-vm':
235
+ ExecutorClass = (await import('./isolated-vm-executor.mjs')).IsolatedVmExecutor;
236
+ break;
237
+
238
+ case 'worker':
239
+ ExecutorClass = (await import('./worker-executor.mjs')).WorkerExecutor;
240
+ break;
241
+
242
+ case 'vm2':
243
+ console.warn('[DEPRECATION] vm2 executor is deprecated and has security vulnerabilities');
244
+ ExecutorClass = (await import('./vm2-executor.mjs')).Vm2Executor;
245
+ break;
246
+
247
+ case 'browser':
248
+ ExecutorClass = (await import('./browser-executor.mjs')).BrowserExecutor;
249
+ break;
250
+
251
+ default:
252
+ throw new Error(`Unknown executor type: ${executorType}`);
253
+ }
254
+
255
+ const executor = new ExecutorClass(config);
256
+ span.setStatus({ code: 1 });
257
+ return executor;
258
+ } catch (error) {
259
+ span.recordException(error);
260
+ span.setStatus({ code: 2, message: error.message });
261
+ throw error;
262
+ } finally {
263
+ span.end();
264
+ }
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Auto-detect and create best executor
270
+ * @param {Object} [config] - Executor configuration
271
+ * @param {Object} [detectionOptions] - Detection options
272
+ * @returns {Promise<Object>} Executor instance
273
+ */
274
+ export async function createBestExecutor(config = {}, detectionOptions = {}) {
275
+ return tracer.startActiveSpan('detector.createBestExecutor', async span => {
276
+ try {
277
+ const executorType = await detectBestExecutor(detectionOptions);
278
+ span.setAttribute('detector.selectedExecutor', executorType);
279
+
280
+ const executor = await createExecutor(executorType, config);
281
+ span.setStatus({ code: 1 });
282
+
283
+ return executor;
284
+ } catch (error) {
285
+ span.recordException(error);
286
+ span.setStatus({ code: 2, message: error.message });
287
+ throw error;
288
+ } finally {
289
+ span.end();
290
+ }
291
+ });
292
+ }
293
+
294
+ /**
295
+ * Get executor capabilities
296
+ * @param {string} executorType - Executor type
297
+ * @returns {Object} Capabilities object
298
+ */
299
+ export function getExecutorCapabilities(executorType) {
300
+ const capabilities = {
301
+ 'isolated-vm': {
302
+ memoryIsolation: 'full',
303
+ cpuIsolation: 'full',
304
+ asyncSupport: true,
305
+ wasmSupport: true,
306
+ securityLevel: 'high',
307
+ performance: 'high',
308
+ overhead: 'low',
309
+ },
310
+ worker: {
311
+ memoryIsolation: 'partial',
312
+ cpuIsolation: 'partial',
313
+ asyncSupport: true,
314
+ wasmSupport: false,
315
+ securityLevel: 'medium',
316
+ performance: 'medium',
317
+ overhead: 'medium',
318
+ },
319
+ vm2: {
320
+ memoryIsolation: 'weak',
321
+ cpuIsolation: 'none',
322
+ asyncSupport: false,
323
+ wasmSupport: false,
324
+ securityLevel: 'low',
325
+ performance: 'medium',
326
+ overhead: 'low',
327
+ deprecated: true,
328
+ securityIssues: true,
329
+ },
330
+ browser: {
331
+ memoryIsolation: 'partial',
332
+ cpuIsolation: 'partial',
333
+ asyncSupport: true,
334
+ wasmSupport: true,
335
+ securityLevel: 'medium',
336
+ performance: 'low',
337
+ overhead: 'high',
338
+ },
339
+ };
340
+
341
+ return capabilities[executorType] || null;
342
+ }