@objectstack/core 0.9.1 → 0.9.2

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 (89) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +7 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +24 -11
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +20 -20
  7. package/dist/dependency-resolver.d.ts +62 -0
  8. package/dist/dependency-resolver.d.ts.map +1 -0
  9. package/dist/dependency-resolver.js +317 -0
  10. package/dist/dependency-resolver.test.d.ts +2 -0
  11. package/dist/dependency-resolver.test.d.ts.map +1 -0
  12. package/dist/dependency-resolver.test.js +241 -0
  13. package/dist/health-monitor.d.ts +65 -0
  14. package/dist/health-monitor.d.ts.map +1 -0
  15. package/dist/health-monitor.js +269 -0
  16. package/dist/health-monitor.test.d.ts +2 -0
  17. package/dist/health-monitor.test.d.ts.map +1 -0
  18. package/dist/health-monitor.test.js +68 -0
  19. package/dist/hot-reload.d.ts +79 -0
  20. package/dist/hot-reload.d.ts.map +1 -0
  21. package/dist/hot-reload.js +313 -0
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -1
  25. package/dist/kernel-base.d.ts +2 -2
  26. package/dist/kernel-base.js +2 -2
  27. package/dist/kernel.d.ts +79 -31
  28. package/dist/kernel.d.ts.map +1 -1
  29. package/dist/kernel.js +383 -73
  30. package/dist/kernel.test.js +373 -122
  31. package/dist/lite-kernel.d.ts +55 -0
  32. package/dist/lite-kernel.d.ts.map +1 -0
  33. package/dist/lite-kernel.js +112 -0
  34. package/dist/lite-kernel.test.d.ts +2 -0
  35. package/dist/lite-kernel.test.d.ts.map +1 -0
  36. package/dist/lite-kernel.test.js +161 -0
  37. package/dist/logger.d.ts +2 -2
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/logger.js +26 -7
  40. package/dist/plugin-loader.d.ts +11 -0
  41. package/dist/plugin-loader.d.ts.map +1 -1
  42. package/dist/plugin-loader.js +34 -10
  43. package/dist/plugin-loader.test.js +9 -0
  44. package/dist/security/index.d.ts +3 -0
  45. package/dist/security/index.d.ts.map +1 -1
  46. package/dist/security/index.js +4 -0
  47. package/dist/security/permission-manager.d.ts +96 -0
  48. package/dist/security/permission-manager.d.ts.map +1 -0
  49. package/dist/security/permission-manager.js +235 -0
  50. package/dist/security/permission-manager.test.d.ts +2 -0
  51. package/dist/security/permission-manager.test.d.ts.map +1 -0
  52. package/dist/security/permission-manager.test.js +220 -0
  53. package/dist/security/sandbox-runtime.d.ts +115 -0
  54. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  55. package/dist/security/sandbox-runtime.js +310 -0
  56. package/dist/security/security-scanner.d.ts +92 -0
  57. package/dist/security/security-scanner.d.ts.map +1 -0
  58. package/dist/security/security-scanner.js +273 -0
  59. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  60. package/examples/phase2-integration.ts +355 -0
  61. package/package.json +2 -2
  62. package/src/api-registry-plugin.test.ts +20 -20
  63. package/src/dependency-resolver.test.ts +287 -0
  64. package/src/dependency-resolver.ts +388 -0
  65. package/src/health-monitor.test.ts +81 -0
  66. package/src/health-monitor.ts +316 -0
  67. package/src/hot-reload.ts +388 -0
  68. package/src/index.ts +6 -1
  69. package/src/kernel-base.ts +2 -2
  70. package/src/kernel.test.ts +469 -134
  71. package/src/kernel.ts +464 -78
  72. package/src/lite-kernel.test.ts +200 -0
  73. package/src/lite-kernel.ts +135 -0
  74. package/src/logger.ts +28 -7
  75. package/src/plugin-loader.test.ts +10 -1
  76. package/src/plugin-loader.ts +42 -13
  77. package/src/security/index.ts +19 -0
  78. package/src/security/permission-manager.test.ts +256 -0
  79. package/src/security/permission-manager.ts +336 -0
  80. package/src/security/sandbox-runtime.ts +432 -0
  81. package/src/security/security-scanner.ts +365 -0
  82. package/dist/enhanced-kernel.d.ts +0 -103
  83. package/dist/enhanced-kernel.d.ts.map +0 -1
  84. package/dist/enhanced-kernel.js +0 -403
  85. package/dist/enhanced-kernel.test.d.ts +0 -2
  86. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  87. package/dist/enhanced-kernel.test.js +0 -412
  88. package/src/enhanced-kernel.test.ts +0 -535
  89. package/src/enhanced-kernel.ts +0 -496
@@ -0,0 +1,432 @@
1
+ import type {
2
+ SandboxConfig
3
+ } from '@objectstack/spec/system';
4
+ import type { ObjectLogger } from '../logger.js';
5
+
6
+ /**
7
+ * Resource Usage Statistics
8
+ */
9
+ export interface ResourceUsage {
10
+ memory: {
11
+ current: number;
12
+ peak: number;
13
+ limit?: number;
14
+ };
15
+ cpu: {
16
+ current: number;
17
+ average: number;
18
+ limit?: number;
19
+ };
20
+ connections: {
21
+ current: number;
22
+ limit?: number;
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Sandbox Execution Context
28
+ * Represents an isolated execution environment for a plugin
29
+ */
30
+ export interface SandboxContext {
31
+ pluginId: string;
32
+ config: SandboxConfig;
33
+ startTime: Date;
34
+ resourceUsage: ResourceUsage;
35
+ }
36
+
37
+ /**
38
+ * Plugin Sandbox Runtime
39
+ *
40
+ * Provides isolated execution environments for plugins with resource limits
41
+ * and access controls
42
+ */
43
+ export class PluginSandboxRuntime {
44
+ private logger: ObjectLogger;
45
+
46
+ // Active sandboxes (pluginId -> context)
47
+ private sandboxes = new Map<string, SandboxContext>();
48
+
49
+ // Resource monitoring intervals
50
+ private monitoringIntervals = new Map<string, NodeJS.Timeout>();
51
+
52
+ constructor(logger: ObjectLogger) {
53
+ this.logger = logger.child({ component: 'SandboxRuntime' });
54
+ }
55
+
56
+ /**
57
+ * Create a sandbox for a plugin
58
+ */
59
+ createSandbox(pluginId: string, config: SandboxConfig): SandboxContext {
60
+ if (this.sandboxes.has(pluginId)) {
61
+ throw new Error(`Sandbox already exists for plugin: ${pluginId}`);
62
+ }
63
+
64
+ const context: SandboxContext = {
65
+ pluginId,
66
+ config,
67
+ startTime: new Date(),
68
+ resourceUsage: {
69
+ memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },
70
+ cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },
71
+ connections: { current: 0, limit: config.network?.maxConnections },
72
+ },
73
+ };
74
+
75
+ this.sandboxes.set(pluginId, context);
76
+
77
+ // Start resource monitoring
78
+ this.startResourceMonitoring(pluginId);
79
+
80
+ this.logger.info('Sandbox created', {
81
+ pluginId,
82
+ level: config.level,
83
+ memoryLimit: config.memory?.maxHeap,
84
+ cpuLimit: config.cpu?.maxCpuPercent
85
+ });
86
+
87
+ return context;
88
+ }
89
+
90
+ /**
91
+ * Destroy a sandbox
92
+ */
93
+ destroySandbox(pluginId: string): void {
94
+ const context = this.sandboxes.get(pluginId);
95
+ if (!context) {
96
+ return;
97
+ }
98
+
99
+ // Stop monitoring
100
+ this.stopResourceMonitoring(pluginId);
101
+
102
+ this.sandboxes.delete(pluginId);
103
+
104
+ this.logger.info('Sandbox destroyed', { pluginId });
105
+ }
106
+
107
+ /**
108
+ * Check if resource access is allowed
109
+ */
110
+ checkResourceAccess(
111
+ pluginId: string,
112
+ resourceType: 'file' | 'network' | 'process' | 'env',
113
+ resourcePath?: string
114
+ ): { allowed: boolean; reason?: string } {
115
+ const context = this.sandboxes.get(pluginId);
116
+ if (!context) {
117
+ return { allowed: false, reason: 'Sandbox not found' };
118
+ }
119
+
120
+ const { config } = context;
121
+
122
+ switch (resourceType) {
123
+ case 'file':
124
+ return this.checkFileAccess(config, resourcePath);
125
+
126
+ case 'network':
127
+ return this.checkNetworkAccess(config, resourcePath);
128
+
129
+ case 'process':
130
+ return this.checkProcessAccess(config);
131
+
132
+ case 'env':
133
+ return this.checkEnvAccess(config, resourcePath);
134
+
135
+ default:
136
+ return { allowed: false, reason: 'Unknown resource type' };
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Check file system access
142
+ * WARNING: Uses simple prefix matching. For production, use proper path
143
+ * resolution with path.resolve() and path.normalize() to prevent traversal.
144
+ */
145
+ private checkFileAccess(
146
+ config: SandboxConfig,
147
+ path?: string
148
+ ): { allowed: boolean; reason?: string } {
149
+ if (config.level === 'none') {
150
+ return { allowed: true };
151
+ }
152
+
153
+ if (!config.filesystem) {
154
+ return { allowed: false, reason: 'File system access not configured' };
155
+ }
156
+
157
+ // If no path specified, check general access
158
+ if (!path) {
159
+ return { allowed: config.filesystem.mode !== 'none' };
160
+ }
161
+
162
+ // TODO: Use path.resolve() and path.normalize() for production
163
+ // Check allowed paths
164
+ const allowedPaths = config.filesystem.allowedPaths || [];
165
+ const isAllowed = allowedPaths.some(allowed => {
166
+ // Simple prefix matching - vulnerable to traversal attacks
167
+ // TODO: Use proper path resolution
168
+ return path.startsWith(allowed);
169
+ });
170
+
171
+ if (allowedPaths.length > 0 && !isAllowed) {
172
+ return {
173
+ allowed: false,
174
+ reason: `Path not in allowed list: ${path}`
175
+ };
176
+ }
177
+
178
+ // Check denied paths
179
+ const deniedPaths = config.filesystem.deniedPaths || [];
180
+ const isDenied = deniedPaths.some(denied => {
181
+ return path.startsWith(denied);
182
+ });
183
+
184
+ if (isDenied) {
185
+ return {
186
+ allowed: false,
187
+ reason: `Path is explicitly denied: ${path}`
188
+ };
189
+ }
190
+
191
+ return { allowed: true };
192
+ }
193
+
194
+ /**
195
+ * Check network access
196
+ * WARNING: Uses simple string matching. For production, use proper URL
197
+ * parsing with new URL() and check hostname property.
198
+ */
199
+ private checkNetworkAccess(
200
+ config: SandboxConfig,
201
+ url?: string
202
+ ): { allowed: boolean; reason?: string } {
203
+ if (config.level === 'none') {
204
+ return { allowed: true };
205
+ }
206
+
207
+ if (!config.network) {
208
+ return { allowed: false, reason: 'Network access not configured' };
209
+ }
210
+
211
+ // Check if network access is enabled
212
+ if (config.network.mode === 'none') {
213
+ return { allowed: false, reason: 'Network access disabled' };
214
+ }
215
+
216
+ // If no URL specified, check general access
217
+ if (!url) {
218
+ return { allowed: (config.network.mode as string) !== 'none' };
219
+ }
220
+
221
+ // TODO: Use new URL() and check hostname property for production
222
+ // Check allowed hosts
223
+ const allowedHosts = config.network.allowedHosts || [];
224
+ if (allowedHosts.length > 0) {
225
+ const isAllowed = allowedHosts.some(host => {
226
+ // Simple string matching - vulnerable to bypass
227
+ // TODO: Use proper URL parsing
228
+ return url.includes(host);
229
+ });
230
+
231
+ if (!isAllowed) {
232
+ return {
233
+ allowed: false,
234
+ reason: `Host not in allowed list: ${url}`
235
+ };
236
+ }
237
+ }
238
+
239
+ // Check denied hosts
240
+ const deniedHosts = config.network.deniedHosts || [];
241
+ const isDenied = deniedHosts.some(host => {
242
+ return url.includes(host);
243
+ });
244
+
245
+ if (isDenied) {
246
+ return {
247
+ allowed: false,
248
+ reason: `Host is blocked: ${url}`
249
+ };
250
+ }
251
+
252
+ return { allowed: true };
253
+ }
254
+
255
+ /**
256
+ * Check process spawning access
257
+ */
258
+ private checkProcessAccess(
259
+ config: SandboxConfig
260
+ ): { allowed: boolean; reason?: string } {
261
+ if (config.level === 'none') {
262
+ return { allowed: true };
263
+ }
264
+
265
+ if (!config.process) {
266
+ return { allowed: false, reason: 'Process access not configured' };
267
+ }
268
+
269
+ if (!config.process.allowSpawn) {
270
+ return { allowed: false, reason: 'Process spawning not allowed' };
271
+ }
272
+
273
+ return { allowed: true };
274
+ }
275
+
276
+ /**
277
+ * Check environment variable access
278
+ */
279
+ private checkEnvAccess(
280
+ config: SandboxConfig,
281
+ varName?: string
282
+ ): { allowed: boolean; reason?: string } {
283
+ if (config.level === 'none') {
284
+ return { allowed: true };
285
+ }
286
+
287
+ if (!config.process) {
288
+ return { allowed: false, reason: 'Environment access not configured' };
289
+ }
290
+
291
+ // If no variable specified, check general access
292
+ if (!varName) {
293
+ return { allowed: true };
294
+ }
295
+
296
+ // For now, allow all env access if process is configured
297
+ // In a real implementation, would check specific allowed vars
298
+ return { allowed: true };
299
+ }
300
+
301
+ /**
302
+ * Check resource limits
303
+ */
304
+ checkResourceLimits(pluginId: string): {
305
+ withinLimits: boolean;
306
+ violations: string[]
307
+ } {
308
+ const context = this.sandboxes.get(pluginId);
309
+ if (!context) {
310
+ return { withinLimits: true, violations: [] };
311
+ }
312
+
313
+ const violations: string[] = [];
314
+ const { resourceUsage, config } = context;
315
+
316
+ // Check memory limit
317
+ if (config.memory?.maxHeap &&
318
+ resourceUsage.memory.current > config.memory.maxHeap) {
319
+ violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);
320
+ }
321
+
322
+ // Check CPU limit (would need runtime config)
323
+ if (config.runtime?.resourceLimits?.maxCpu &&
324
+ resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {
325
+ violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);
326
+ }
327
+
328
+ // Check connection limit
329
+ if (config.network?.maxConnections &&
330
+ resourceUsage.connections.current > config.network.maxConnections) {
331
+ violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);
332
+ }
333
+
334
+ return {
335
+ withinLimits: violations.length === 0,
336
+ violations,
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Get resource usage for a plugin
342
+ */
343
+ getResourceUsage(pluginId: string): ResourceUsage | undefined {
344
+ const context = this.sandboxes.get(pluginId);
345
+ return context?.resourceUsage;
346
+ }
347
+
348
+ /**
349
+ * Start monitoring resource usage
350
+ */
351
+ private startResourceMonitoring(pluginId: string): void {
352
+ // Monitor every 5 seconds
353
+ const interval = setInterval(() => {
354
+ this.updateResourceUsage(pluginId);
355
+ }, 5000);
356
+
357
+ this.monitoringIntervals.set(pluginId, interval);
358
+ }
359
+
360
+ /**
361
+ * Stop monitoring resource usage
362
+ */
363
+ private stopResourceMonitoring(pluginId: string): void {
364
+ const interval = this.monitoringIntervals.get(pluginId);
365
+ if (interval) {
366
+ clearInterval(interval);
367
+ this.monitoringIntervals.delete(pluginId);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Update resource usage statistics
373
+ *
374
+ * NOTE: Currently uses global process.memoryUsage() which tracks the entire
375
+ * Node.js process, not individual plugins. For production, implement proper
376
+ * per-plugin tracking using V8 heap snapshots or allocation tracking at
377
+ * plugin boundaries.
378
+ */
379
+ private updateResourceUsage(pluginId: string): void {
380
+ const context = this.sandboxes.get(pluginId);
381
+ if (!context) {
382
+ return;
383
+ }
384
+
385
+ // In a real implementation, this would collect actual metrics
386
+ // For now, this is a placeholder structure
387
+
388
+ // Update memory usage (global process memory - not per-plugin)
389
+ // TODO: Implement per-plugin memory tracking
390
+ const memoryUsage = process.memoryUsage();
391
+ context.resourceUsage.memory.current = memoryUsage.heapUsed;
392
+ context.resourceUsage.memory.peak = Math.max(
393
+ context.resourceUsage.memory.peak,
394
+ memoryUsage.heapUsed
395
+ );
396
+
397
+ // Update CPU usage (would use process.cpuUsage() or similar)
398
+ // This is a placeholder - real implementation would track per-plugin CPU
399
+ // TODO: Implement per-plugin CPU tracking
400
+ context.resourceUsage.cpu.current = 0;
401
+
402
+ // Check for violations
403
+ const { withinLimits, violations } = this.checkResourceLimits(pluginId);
404
+ if (!withinLimits) {
405
+ this.logger.warn('Resource limit violations detected', {
406
+ pluginId,
407
+ violations
408
+ });
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Get all active sandboxes
414
+ */
415
+ getAllSandboxes(): Map<string, SandboxContext> {
416
+ return new Map(this.sandboxes);
417
+ }
418
+
419
+ /**
420
+ * Shutdown sandbox runtime
421
+ */
422
+ shutdown(): void {
423
+ // Stop all monitoring
424
+ for (const pluginId of this.monitoringIntervals.keys()) {
425
+ this.stopResourceMonitoring(pluginId);
426
+ }
427
+
428
+ this.sandboxes.clear();
429
+
430
+ this.logger.info('Sandbox runtime shutdown complete');
431
+ }
432
+ }