@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,331 @@
1
+ /**
2
+ * @file Sandbox Restrictions for Hook Execution
3
+ * @module sandbox-restrictions
4
+ *
5
+ * @description
6
+ * Defines and enforces security restrictions for sandboxed hook execution.
7
+ * Prevents privilege escalation and unauthorized system access.
8
+ */
9
+
10
+ import { z } from 'zod';
11
+
12
+ /**
13
+ * Schema for sandbox configuration
14
+ */
15
+ const SandboxConfigSchema = z
16
+ .object({
17
+ allowFileSystem: z.boolean().default(false),
18
+ allowNetwork: z.boolean().default(false),
19
+ allowProcessAccess: z.boolean().default(false),
20
+ allowEval: z.boolean().default(false),
21
+ timeoutMs: z.number().default(5000),
22
+ memoryLimitMB: z.number().default(50),
23
+ maxIterations: z.number().default(100000),
24
+ })
25
+ .strict();
26
+
27
+ /**
28
+ * Dangerous Node.js modules that should be blocked
29
+ */
30
+ const BLOCKED_MODULES = new Set([
31
+ 'fs',
32
+ 'fs/promises',
33
+ 'child_process',
34
+ 'cluster',
35
+ 'crypto',
36
+ 'dgram',
37
+ 'dns',
38
+ 'http',
39
+ 'https',
40
+ 'http2',
41
+ 'inspector',
42
+ 'net',
43
+ 'os',
44
+ 'perf_hooks',
45
+ 'process',
46
+ 'repl',
47
+ 'tls',
48
+ 'tty',
49
+ 'v8',
50
+ 'vm',
51
+ 'worker_threads',
52
+ 'zlib',
53
+ ]);
54
+
55
+ /**
56
+ * Dangerous global objects/functions
57
+ */
58
+ const BLOCKED_GLOBALS = new Set([
59
+ 'eval',
60
+ 'Function',
61
+ 'require',
62
+ 'import',
63
+ 'process',
64
+ 'global',
65
+ '__dirname',
66
+ '__filename',
67
+ 'Buffer',
68
+ 'clearImmediate',
69
+ 'setImmediate',
70
+ 'clearInterval',
71
+ 'clearTimeout',
72
+ 'setInterval',
73
+ 'setTimeout',
74
+ ]);
75
+
76
+ /**
77
+ * Sandbox Restrictions Manager
78
+ */
79
+ export class SandboxRestrictions {
80
+ /**
81
+ * @param {Object} [config] - Sandbox configuration
82
+ */
83
+ constructor(config = {}) {
84
+ this.config = SandboxConfigSchema.parse(config);
85
+ this.iterationCount = 0;
86
+ this.startTime = null;
87
+ }
88
+
89
+ /**
90
+ * Create a restricted context for hook execution
91
+ * @returns {Object} Restricted context object
92
+ */
93
+ createRestrictedContext() {
94
+ const context = {
95
+ // Allow safe Math operations
96
+ Math: Math,
97
+
98
+ // Allow safe JSON operations
99
+ JSON: JSON,
100
+
101
+ // Allow safe Date operations (read-only)
102
+ Date: Date,
103
+
104
+ // Allow safe String/Number/Boolean/Array operations
105
+ String: String,
106
+ Number: Number,
107
+ Boolean: Boolean,
108
+ Array: Array,
109
+ Object: Object,
110
+
111
+ // Logging (safe, write-only)
112
+ console: {
113
+ log: (...args) => console.log('[Sandboxed]', ...args),
114
+ error: (...args) => console.error('[Sandboxed]', ...args),
115
+ warn: (...args) => console.warn('[Sandboxed]', ...args),
116
+ },
117
+
118
+ // Block dangerous functions
119
+ eval: undefined,
120
+ Function: undefined,
121
+ require: undefined,
122
+ import: undefined,
123
+ process: undefined,
124
+ global: undefined,
125
+ __dirname: undefined,
126
+ __filename: undefined,
127
+ Buffer: undefined,
128
+
129
+ // Block timers (DoS prevention)
130
+ setTimeout: undefined,
131
+ setInterval: undefined,
132
+ setImmediate: undefined,
133
+ clearTimeout: undefined,
134
+ clearInterval: undefined,
135
+ clearImmediate: undefined,
136
+ };
137
+
138
+ // Freeze context to prevent modification
139
+ return Object.freeze(context);
140
+ }
141
+
142
+ /**
143
+ * Validate hook function code for dangerous patterns
144
+ * @param {Function} hookFn - Hook function to validate
145
+ * @returns {Object} Validation result { valid, violations }
146
+ */
147
+ validateHookCode(hookFn) {
148
+ if (typeof hookFn !== 'function') {
149
+ return {
150
+ valid: false,
151
+ violations: ['Hook must be a function'],
152
+ };
153
+ }
154
+
155
+ const codeString = hookFn.toString();
156
+ const violations = [];
157
+
158
+ // Check for blocked module requires
159
+ for (const moduleName of BLOCKED_MODULES) {
160
+ if (codeString.includes(`require('${moduleName}')`)) {
161
+ violations.push(`Blocked module access: ${moduleName}`);
162
+ }
163
+ if (codeString.includes(`require("${moduleName}")`)) {
164
+ violations.push(`Blocked module access: ${moduleName}`);
165
+ }
166
+ if (codeString.includes(`from '${moduleName}'`)) {
167
+ violations.push(`Blocked module import: ${moduleName}`);
168
+ }
169
+ }
170
+
171
+ // Check for blocked globals
172
+ for (const globalName of BLOCKED_GLOBALS) {
173
+ // Use word boundaries to avoid false positives
174
+ const pattern = new RegExp(`\\b${globalName}\\b`, 'g');
175
+ if (pattern.test(codeString)) {
176
+ violations.push(`Blocked global access: ${globalName}`);
177
+ }
178
+ }
179
+
180
+ // Check for file system access patterns
181
+ if (!this.config.allowFileSystem) {
182
+ if (/\bfs\./g.test(codeString) || /readFileSync|writeFileSync/g.test(codeString)) {
183
+ violations.push('File system access not allowed');
184
+ }
185
+ }
186
+
187
+ // Check for network access patterns
188
+ if (!this.config.allowNetwork) {
189
+ if (/\bfetch\(|XMLHttpRequest|WebSocket/g.test(codeString)) {
190
+ violations.push('Network access not allowed');
191
+ }
192
+ }
193
+
194
+ // Check for process access
195
+ if (!this.config.allowProcessAccess) {
196
+ if (/\bprocess\./g.test(codeString)) {
197
+ violations.push('Process access not allowed');
198
+ }
199
+ }
200
+
201
+ // Check for eval usage
202
+ if (!this.config.allowEval) {
203
+ if (/\beval\(|new Function\(/g.test(codeString)) {
204
+ violations.push('Dynamic code evaluation not allowed');
205
+ }
206
+ }
207
+
208
+ return {
209
+ valid: violations.length === 0,
210
+ violations,
211
+ };
212
+ }
213
+
214
+ /**
215
+ * Execute a hook function with restrictions
216
+ * @param {Function} hookFn - Hook function to execute
217
+ * @param {Object} event - Event object
218
+ * @returns {Promise<Object>} Execution result
219
+ */
220
+ async executeRestricted(hookFn, event) {
221
+ // Validate code before execution
222
+ const validation = this.validateHookCode(hookFn);
223
+ if (!validation.valid) {
224
+ return {
225
+ success: false,
226
+ error: `Security validation failed: ${validation.violations.join(', ')}`,
227
+ };
228
+ }
229
+
230
+ this.startTime = Date.now();
231
+ this.iterationCount = 0;
232
+
233
+ try {
234
+ // Create restricted context
235
+ const restrictedContext = this.createRestrictedContext();
236
+
237
+ // Execute with timeout
238
+ const timeoutPromise = new Promise((_, reject) => {
239
+ setTimeout(() => {
240
+ reject(new Error(`Execution timeout after ${this.config.timeoutMs}ms`));
241
+ }, this.config.timeoutMs);
242
+ });
243
+
244
+ // Wrap hook function to prevent context mutation
245
+ const wrappedFn = async () => {
246
+ try {
247
+ // Prevent event mutation by freezing
248
+ const frozenEvent = this._deepFreeze({ ...event });
249
+
250
+ // Execute in restricted context
251
+ const result = await hookFn.call(restrictedContext, frozenEvent);
252
+
253
+ // Check iteration limit
254
+ if (this.iterationCount > this.config.maxIterations) {
255
+ throw new Error('Maximum iteration count exceeded');
256
+ }
257
+
258
+ return result;
259
+ } catch (error) {
260
+ // Block system access errors
261
+ if (error.code === 'EACCES' || error.code === 'EPERM') {
262
+ return {
263
+ success: false,
264
+ error: 'System access denied',
265
+ };
266
+ }
267
+ throw error;
268
+ }
269
+ };
270
+
271
+ const result = await Promise.race([wrappedFn(), timeoutPromise]);
272
+
273
+ return result || { success: true };
274
+ } catch (error) {
275
+ if (error.message.includes('timeout')) {
276
+ return {
277
+ success: false,
278
+ error: 'Execution timeout exceeded',
279
+ };
280
+ }
281
+
282
+ if (error.message.includes('iteration')) {
283
+ return {
284
+ success: false,
285
+ error: 'Maximum iteration count exceeded',
286
+ };
287
+ }
288
+
289
+ return {
290
+ success: false,
291
+ error: error.message || 'Hook execution failed',
292
+ };
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Deep freeze an object to prevent mutation
298
+ * @param {Object} obj - Object to freeze
299
+ * @returns {Object} Frozen object
300
+ * @private
301
+ */
302
+ _deepFreeze(obj) {
303
+ if (obj === null || typeof obj !== 'object') {
304
+ return obj;
305
+ }
306
+
307
+ Object.freeze(obj);
308
+
309
+ Object.getOwnPropertyNames(obj).forEach(prop => {
310
+ if (obj[prop] !== null && typeof obj[prop] === 'object') {
311
+ this._deepFreeze(obj[prop]);
312
+ }
313
+ });
314
+
315
+ return obj;
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Create sandbox restrictions instance
321
+ * @param {Object} [config] - Sandbox configuration
322
+ * @returns {SandboxRestrictions} Restrictions instance
323
+ */
324
+ export function createSandboxRestrictions(config = {}) {
325
+ return new SandboxRestrictions(config);
326
+ }
327
+
328
+ /**
329
+ * Default sandbox restrictions (strict mode)
330
+ */
331
+ export const defaultSandboxRestrictions = new SandboxRestrictions();
@@ -0,0 +1,167 @@
1
+ /**
2
+ * @fileoverview Batched OTEL Telemetry - Reduces span overhead
3
+ *
4
+ * @description
5
+ * Optimizes OpenTelemetry instrumentation to reduce span creation overhead:
6
+ * - Single parent span per transaction (not per hook)
7
+ * - Async attribute setting (non-blocking)
8
+ * - Conditional instrumentation (disable in production)
9
+ * - Batch flush for pending attributes
10
+ *
11
+ * Expected Impact: 10-15% latency reduction
12
+ *
13
+ * @module knowledge-engine/telemetry
14
+ */
15
+
16
+ /**
17
+ * Batched OTEL Telemetry for Knowledge Hooks
18
+ *
19
+ * @class BatchedTelemetry
20
+ */
21
+ export class BatchedTelemetry {
22
+ /**
23
+ * Create a new batched telemetry instance
24
+ * @param {object} tracer - OpenTelemetry tracer instance
25
+ * @param {object} options - Configuration options
26
+ * @param {boolean} options.enabled - Enable telemetry (default: true if not in production)
27
+ * @param {number} options.flushInterval - Batch flush interval in ms (default: 10ms)
28
+ */
29
+ constructor(tracer, options = {}) {
30
+ this.tracer = tracer;
31
+ this.enabled = options.enabled ?? process.env.NODE_ENV !== 'production';
32
+ this.flushInterval = options.flushInterval ?? 10;
33
+ this.pendingAttributes = [];
34
+ this.flushTimeout = null;
35
+ }
36
+
37
+ /**
38
+ * Start parent span for entire transaction (not per hook)
39
+ *
40
+ * @param {string} name - Span name
41
+ * @param {object} attributes - Initial attributes
42
+ * @returns {object|null} Span instance or null if disabled
43
+ */
44
+ startTransactionSpan(name, attributes = {}) {
45
+ if (!this.enabled || !this.tracer) {
46
+ return null;
47
+ }
48
+
49
+ try {
50
+ return this.tracer.startSpan(name, {
51
+ attributes: {
52
+ 'hook.transaction': true,
53
+ ...attributes,
54
+ },
55
+ });
56
+ } catch {
57
+ return null;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Queue attribute update for batch flush (async, non-blocking)
63
+ *
64
+ * Attributes are queued and flushed in batch after flushInterval
65
+ * to avoid blocking the hot path with span attribute mutations.
66
+ *
67
+ * @param {object} span - Span instance
68
+ * @param {string} key - Attribute key
69
+ * @param {*} value - Attribute value
70
+ */
71
+ setAttribute(span, key, value) {
72
+ if (!this.enabled || !span) {
73
+ return;
74
+ }
75
+
76
+ // Queue attribute update
77
+ this.pendingAttributes.push({ span, key, value });
78
+
79
+ // Schedule batch flush if not already scheduled
80
+ if (!this.flushTimeout) {
81
+ this.flushTimeout = setTimeout(() => this.flush(), this.flushInterval);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Flush pending attributes in batch
87
+ *
88
+ * This reduces the number of span attribute mutations
89
+ * by batching them together rather than setting each individually.
90
+ */
91
+ flush() {
92
+ try {
93
+ for (const { span, key, value } of this.pendingAttributes) {
94
+ try {
95
+ span.setAttribute(key, value);
96
+ } catch {
97
+ // Ignore individual attribute errors
98
+ }
99
+ }
100
+ } finally {
101
+ this.pendingAttributes = [];
102
+ this.flushTimeout = null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Record span event (batched)
108
+ *
109
+ * @param {object} span - Span instance
110
+ * @param {string} name - Event name
111
+ * @param {object} attributes - Event attributes
112
+ */
113
+ recordEvent(span, name, attributes = {}) {
114
+ if (!this.enabled || !span) {
115
+ return;
116
+ }
117
+
118
+ try {
119
+ span.recordEvent(name, attributes);
120
+ } catch {
121
+ // Ignore recording errors
122
+ }
123
+ }
124
+
125
+ /**
126
+ * End span with status
127
+ *
128
+ * @param {object} span - Span instance
129
+ * @param {string} status - Status code ('ok', 'error', 'unset')
130
+ * @param {string} message - Status message (optional)
131
+ */
132
+ endSpan(span, status = 'ok', message = '') {
133
+ if (!this.enabled || !span) {
134
+ return;
135
+ }
136
+
137
+ try {
138
+ // Flush any pending attributes first
139
+ if (this.pendingAttributes.length > 0) {
140
+ this.flush();
141
+ }
142
+
143
+ // End span with status
144
+ span.setStatus({ code: status, message });
145
+ span.end();
146
+ } catch {
147
+ // Ignore end errors
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Disable telemetry (for production or testing)
153
+ */
154
+ disable() {
155
+ this.enabled = false;
156
+ this.flush();
157
+ }
158
+
159
+ /**
160
+ * Enable telemetry
161
+ */
162
+ enable() {
163
+ this.enabled = true;
164
+ }
165
+ }
166
+
167
+ export default BatchedTelemetry;
package/src/index.mjs ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @unrdf/hooks
3
+ *
4
+ * Knowledge Hooks - Policy Definition and Execution Framework
5
+ *
6
+ * @module @unrdf/hooks
7
+ */
8
+
9
+ // Hook definition
10
+ export {
11
+ defineHook,
12
+ isValidHook,
13
+ getHookMetadata,
14
+ hasValidation,
15
+ hasTransformation,
16
+ HookTriggerSchema,
17
+ HookConfigSchema,
18
+ HookSchema,
19
+ } from './hooks/define-hook.mjs';
20
+
21
+ // Hook execution
22
+ export {
23
+ executeHook,
24
+ executeHookChain,
25
+ executeHooksByTrigger,
26
+ wouldPassHooks,
27
+ HookResultSchema,
28
+ ChainResultSchema,
29
+ // Validation-only execution
30
+ validateOnly,
31
+ // Cache management
32
+ clearHookCaches,
33
+ prewarmHookCache,
34
+ // Batch API (high-performance bulk operations)
35
+ executeBatch,
36
+ validateBatch,
37
+ transformBatch,
38
+ } from './hooks/hook-executor.mjs';
39
+
40
+ // Hook chain compiler (JIT optimization)
41
+ export {
42
+ compileHookChain,
43
+ compileValidationOnlyChain,
44
+ clearCompiledChainCache,
45
+ getCompilerStats,
46
+ isJitAvailable,
47
+ getChainKey,
48
+ } from './hooks/hook-chain-compiler.mjs';
49
+
50
+ // Quad pool (object pooling optimization)
51
+ export { QuadPool, quadPool, createPooledTransform, isPooledQuad } from './hooks/quad-pool.mjs';
52
+
53
+ // Hook management
54
+ export {
55
+ createHookRegistry,
56
+ registerHook,
57
+ unregisterHook,
58
+ getHook,
59
+ listHooks,
60
+ getHooksByTrigger,
61
+ hasHook,
62
+ clearHooks,
63
+ getRegistryStats,
64
+ HookRegistrySchema,
65
+ } from './hooks/hook-management.mjs';
66
+
67
+ // Built-in hooks
68
+ export {
69
+ builtinHooks,
70
+ validateSubjectIRI,
71
+ validatePredicateIRI,
72
+ validateObjectLiteral,
73
+ validateIRIFormat,
74
+ validateLanguageTag,
75
+ rejectBlankNodes,
76
+ normalizeNamespace,
77
+ normalizeLanguageTag,
78
+ trimLiterals,
79
+ // Pooled variants (zero-allocation transforms)
80
+ normalizeLanguageTagPooled,
81
+ trimLiteralsPooled,
82
+ standardValidation,
83
+ } from './hooks/builtin-hooks.mjs';
84
+
85
+ // Hook manager (class-based interface)
86
+ export { KnowledgeHookManager } from './hooks/knowledge-hook-manager.mjs';
87
+
88
+ // Hook scheduler (cron/interval triggers)
89
+ export {
90
+ HookScheduler,
91
+ createHookScheduler,
92
+ ScheduleConfigSchema,
93
+ } from './hooks/hook-scheduler.mjs';
94
+
95
+ // Quality metrics (Lean Six Sigma hooks)
96
+ export {
97
+ QualityMetricsCollector,
98
+ createQualityHooks,
99
+ QualityGateSchema,
100
+ SPCDataPointSchema,
101
+ } from './hooks/quality-metrics.mjs';