@objectstack/core 4.0.3 → 4.0.5

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