@objectstack/core 1.0.2 → 1.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 (125) hide show
  1. package/.turbo/turbo-build.log +58 -0
  2. package/CHANGELOG.md +25 -0
  3. package/dist/index.cjs +4294 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1777 -0
  6. package/dist/index.d.ts +1776 -21
  7. package/dist/index.js +4246 -23
  8. package/dist/index.js.map +1 -0
  9. package/package.json +4 -4
  10. package/tsconfig.json +1 -3
  11. package/dist/api-registry-plugin.d.ts +0 -54
  12. package/dist/api-registry-plugin.d.ts.map +0 -1
  13. package/dist/api-registry-plugin.js +0 -53
  14. package/dist/api-registry-plugin.test.d.ts +0 -2
  15. package/dist/api-registry-plugin.test.d.ts.map +0 -1
  16. package/dist/api-registry-plugin.test.js +0 -334
  17. package/dist/api-registry.d.ts +0 -259
  18. package/dist/api-registry.d.ts.map +0 -1
  19. package/dist/api-registry.js +0 -600
  20. package/dist/api-registry.test.d.ts +0 -2
  21. package/dist/api-registry.test.d.ts.map +0 -1
  22. package/dist/api-registry.test.js +0 -957
  23. package/dist/contracts/data-engine.d.ts +0 -62
  24. package/dist/contracts/data-engine.d.ts.map +0 -1
  25. package/dist/contracts/data-engine.js +0 -1
  26. package/dist/contracts/http-server.d.ts +0 -119
  27. package/dist/contracts/http-server.d.ts.map +0 -1
  28. package/dist/contracts/http-server.js +0 -11
  29. package/dist/contracts/logger.d.ts +0 -63
  30. package/dist/contracts/logger.d.ts.map +0 -1
  31. package/dist/contracts/logger.js +0 -1
  32. package/dist/dependency-resolver.d.ts +0 -62
  33. package/dist/dependency-resolver.d.ts.map +0 -1
  34. package/dist/dependency-resolver.js +0 -317
  35. package/dist/dependency-resolver.test.d.ts +0 -2
  36. package/dist/dependency-resolver.test.d.ts.map +0 -1
  37. package/dist/dependency-resolver.test.js +0 -241
  38. package/dist/health-monitor.d.ts +0 -65
  39. package/dist/health-monitor.d.ts.map +0 -1
  40. package/dist/health-monitor.js +0 -269
  41. package/dist/health-monitor.test.d.ts +0 -2
  42. package/dist/health-monitor.test.d.ts.map +0 -1
  43. package/dist/health-monitor.test.js +0 -68
  44. package/dist/hot-reload.d.ts +0 -79
  45. package/dist/hot-reload.d.ts.map +0 -1
  46. package/dist/hot-reload.js +0 -313
  47. package/dist/index.d.ts.map +0 -1
  48. package/dist/kernel-base.d.ts +0 -84
  49. package/dist/kernel-base.d.ts.map +0 -1
  50. package/dist/kernel-base.js +0 -219
  51. package/dist/kernel.d.ts +0 -113
  52. package/dist/kernel.d.ts.map +0 -1
  53. package/dist/kernel.js +0 -472
  54. package/dist/kernel.test.d.ts +0 -2
  55. package/dist/kernel.test.d.ts.map +0 -1
  56. package/dist/kernel.test.js +0 -414
  57. package/dist/lite-kernel.d.ts +0 -55
  58. package/dist/lite-kernel.d.ts.map +0 -1
  59. package/dist/lite-kernel.js +0 -112
  60. package/dist/lite-kernel.test.d.ts +0 -2
  61. package/dist/lite-kernel.test.d.ts.map +0 -1
  62. package/dist/lite-kernel.test.js +0 -161
  63. package/dist/logger.d.ts +0 -71
  64. package/dist/logger.d.ts.map +0 -1
  65. package/dist/logger.js +0 -312
  66. package/dist/logger.test.d.ts +0 -2
  67. package/dist/logger.test.d.ts.map +0 -1
  68. package/dist/logger.test.js +0 -92
  69. package/dist/plugin-loader.d.ts +0 -164
  70. package/dist/plugin-loader.d.ts.map +0 -1
  71. package/dist/plugin-loader.js +0 -319
  72. package/dist/plugin-loader.test.d.ts +0 -2
  73. package/dist/plugin-loader.test.d.ts.map +0 -1
  74. package/dist/plugin-loader.test.js +0 -348
  75. package/dist/qa/adapter.d.ts +0 -14
  76. package/dist/qa/adapter.d.ts.map +0 -1
  77. package/dist/qa/adapter.js +0 -1
  78. package/dist/qa/http-adapter.d.ts +0 -16
  79. package/dist/qa/http-adapter.d.ts.map +0 -1
  80. package/dist/qa/http-adapter.js +0 -107
  81. package/dist/qa/index.d.ts +0 -4
  82. package/dist/qa/index.d.ts.map +0 -1
  83. package/dist/qa/index.js +0 -3
  84. package/dist/qa/runner.d.ts +0 -27
  85. package/dist/qa/runner.d.ts.map +0 -1
  86. package/dist/qa/runner.js +0 -157
  87. package/dist/security/index.d.ts +0 -17
  88. package/dist/security/index.d.ts.map +0 -1
  89. package/dist/security/index.js +0 -17
  90. package/dist/security/permission-manager.d.ts +0 -96
  91. package/dist/security/permission-manager.d.ts.map +0 -1
  92. package/dist/security/permission-manager.js +0 -235
  93. package/dist/security/permission-manager.test.d.ts +0 -2
  94. package/dist/security/permission-manager.test.d.ts.map +0 -1
  95. package/dist/security/permission-manager.test.js +0 -220
  96. package/dist/security/plugin-config-validator.d.ts +0 -79
  97. package/dist/security/plugin-config-validator.d.ts.map +0 -1
  98. package/dist/security/plugin-config-validator.js +0 -166
  99. package/dist/security/plugin-config-validator.test.d.ts +0 -2
  100. package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
  101. package/dist/security/plugin-config-validator.test.js +0 -223
  102. package/dist/security/plugin-permission-enforcer.d.ts +0 -154
  103. package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
  104. package/dist/security/plugin-permission-enforcer.js +0 -323
  105. package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
  106. package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
  107. package/dist/security/plugin-permission-enforcer.test.js +0 -205
  108. package/dist/security/plugin-signature-verifier.d.ts +0 -96
  109. package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
  110. package/dist/security/plugin-signature-verifier.js +0 -250
  111. package/dist/security/sandbox-runtime.d.ts +0 -115
  112. package/dist/security/sandbox-runtime.d.ts.map +0 -1
  113. package/dist/security/sandbox-runtime.js +0 -311
  114. package/dist/security/security-scanner.d.ts +0 -92
  115. package/dist/security/security-scanner.d.ts.map +0 -1
  116. package/dist/security/security-scanner.js +0 -273
  117. package/dist/types.d.ts +0 -89
  118. package/dist/types.d.ts.map +0 -1
  119. package/dist/types.js +0 -1
  120. package/dist/utils/env.d.ts +0 -20
  121. package/dist/utils/env.d.ts.map +0 -1
  122. package/dist/utils/env.js +0 -46
  123. package/dist/utils/env.test.d.ts +0 -2
  124. package/dist/utils/env.test.d.ts.map +0 -1
  125. package/dist/utils/env.test.js +0 -52
@@ -1,250 +0,0 @@
1
- // Conditionally import crypto for Node.js environments
2
- let cryptoModule = null;
3
- if (typeof globalThis.window === 'undefined') {
4
- try {
5
- // Dynamic import for Node.js crypto module (using eval to avoid bundling issues)
6
- // @ts-ignore - dynamic require for Node.js
7
- cryptoModule = eval('require("crypto")');
8
- }
9
- catch {
10
- // Crypto module not available (e.g., browser environment)
11
- }
12
- }
13
- /**
14
- * Plugin Signature Verifier
15
- *
16
- * Implements cryptographic verification of plugin signatures to ensure:
17
- * 1. Plugin integrity - code hasn't been tampered with
18
- * 2. Publisher authenticity - plugin comes from trusted source
19
- * 3. Non-repudiation - publisher cannot deny signing
20
- *
21
- * Architecture:
22
- * - Uses Node.js crypto module for signature verification
23
- * - Supports RSA (RS256) and ECDSA (ES256) algorithms
24
- * - Verifies against trusted public key registry
25
- * - Computes hash of plugin code for integrity check
26
- *
27
- * Security Model:
28
- * - Public keys are pre-registered and trusted
29
- * - Plugin signature is verified before loading
30
- * - Strict mode rejects unsigned plugins
31
- * - Development mode allows self-signed plugins
32
- */
33
- export class PluginSignatureVerifier {
34
- constructor(config, logger) {
35
- this.config = config;
36
- this.logger = logger;
37
- this.validateConfig();
38
- }
39
- /**
40
- * Verify plugin signature
41
- *
42
- * @param plugin - Plugin metadata with signature
43
- * @returns Verification result
44
- * @throws Error if verification fails in strict mode
45
- */
46
- async verifyPluginSignature(plugin) {
47
- // Handle unsigned plugins
48
- if (!plugin.signature) {
49
- return this.handleUnsignedPlugin(plugin);
50
- }
51
- try {
52
- // 1. Extract publisher ID from plugin name (reverse domain notation)
53
- const publisherId = this.extractPublisherId(plugin.name);
54
- // 2. Get trusted public key for publisher
55
- const publicKey = this.config.trustedPublicKeys.get(publisherId);
56
- if (!publicKey) {
57
- const error = `No trusted public key for publisher: ${publisherId}`;
58
- this.logger.warn(error, { plugin: plugin.name, publisherId });
59
- if (this.config.strictMode && !this.config.allowSelfSigned) {
60
- throw new Error(error);
61
- }
62
- return {
63
- verified: false,
64
- error,
65
- publisherId,
66
- };
67
- }
68
- // 3. Compute plugin code hash
69
- const pluginHash = this.computePluginHash(plugin);
70
- // 4. Verify signature using crypto module
71
- const isValid = await this.verifyCryptoSignature(pluginHash, plugin.signature, publicKey);
72
- if (!isValid) {
73
- const error = `Signature verification failed for plugin: ${plugin.name}`;
74
- this.logger.error(error, undefined, { plugin: plugin.name, publisherId });
75
- throw new Error(error);
76
- }
77
- this.logger.info(`✅ Plugin signature verified: ${plugin.name}`, {
78
- plugin: plugin.name,
79
- publisherId,
80
- algorithm: this.config.algorithm,
81
- });
82
- return {
83
- verified: true,
84
- publisherId,
85
- algorithm: this.config.algorithm,
86
- };
87
- }
88
- catch (error) {
89
- this.logger.error(`Signature verification error: ${plugin.name}`, error);
90
- if (this.config.strictMode) {
91
- throw error;
92
- }
93
- return {
94
- verified: false,
95
- error: error.message,
96
- };
97
- }
98
- }
99
- /**
100
- * Register a trusted public key for a publisher
101
- */
102
- registerPublicKey(publisherId, publicKey) {
103
- this.config.trustedPublicKeys.set(publisherId, publicKey);
104
- this.logger.info(`Trusted public key registered for: ${publisherId}`);
105
- }
106
- /**
107
- * Remove a trusted public key
108
- */
109
- revokePublicKey(publisherId) {
110
- this.config.trustedPublicKeys.delete(publisherId);
111
- this.logger.warn(`Public key revoked for: ${publisherId}`);
112
- }
113
- /**
114
- * Get list of trusted publishers
115
- */
116
- getTrustedPublishers() {
117
- return Array.from(this.config.trustedPublicKeys.keys());
118
- }
119
- // Private methods
120
- handleUnsignedPlugin(plugin) {
121
- if (this.config.strictMode) {
122
- const error = `Plugin missing signature (strict mode): ${plugin.name}`;
123
- this.logger.error(error, undefined, { plugin: plugin.name });
124
- throw new Error(error);
125
- }
126
- this.logger.warn(`⚠️ Plugin not signed: ${plugin.name}`, {
127
- plugin: plugin.name,
128
- recommendation: 'Consider signing plugins for production environments',
129
- });
130
- return {
131
- verified: false,
132
- error: 'Plugin not signed',
133
- };
134
- }
135
- extractPublisherId(pluginName) {
136
- // Extract publisher from reverse domain notation
137
- // Example: "com.objectstack.engine.objectql" -> "com.objectstack"
138
- const parts = pluginName.split('.');
139
- if (parts.length < 2) {
140
- throw new Error(`Invalid plugin name format: ${pluginName} (expected reverse domain notation)`);
141
- }
142
- // Return first two parts (domain reversed)
143
- return `${parts[0]}.${parts[1]}`;
144
- }
145
- computePluginHash(plugin) {
146
- // In browser environment, use SubtleCrypto
147
- if (typeof globalThis.window !== 'undefined') {
148
- return this.computePluginHashBrowser(plugin);
149
- }
150
- // In Node.js environment, use crypto module
151
- return this.computePluginHashNode(plugin);
152
- }
153
- computePluginHashNode(plugin) {
154
- // Use pre-loaded crypto module
155
- if (!cryptoModule) {
156
- this.logger.warn('crypto module not available, using fallback hash');
157
- return this.computePluginHashFallback(plugin);
158
- }
159
- // Compute hash of plugin code
160
- const pluginCode = this.serializePluginCode(plugin);
161
- return cryptoModule.createHash('sha256').update(pluginCode).digest('hex');
162
- }
163
- computePluginHashBrowser(plugin) {
164
- // Browser environment - use simple hash for now
165
- // In production, should use SubtleCrypto for proper cryptographic hash
166
- this.logger.debug('Using browser hash (SubtleCrypto integration pending)');
167
- return this.computePluginHashFallback(plugin);
168
- }
169
- computePluginHashFallback(plugin) {
170
- // Simple hash fallback (not cryptographically secure)
171
- const pluginCode = this.serializePluginCode(plugin);
172
- let hash = 0;
173
- for (let i = 0; i < pluginCode.length; i++) {
174
- const char = pluginCode.charCodeAt(i);
175
- hash = ((hash << 5) - hash) + char;
176
- hash = hash & hash; // Convert to 32-bit integer
177
- }
178
- return hash.toString(16);
179
- }
180
- serializePluginCode(plugin) {
181
- // Serialize plugin code for hashing
182
- // Include init, start, destroy functions
183
- const parts = [
184
- plugin.name,
185
- plugin.version,
186
- plugin.init.toString(),
187
- ];
188
- if (plugin.start) {
189
- parts.push(plugin.start.toString());
190
- }
191
- if (plugin.destroy) {
192
- parts.push(plugin.destroy.toString());
193
- }
194
- return parts.join('|');
195
- }
196
- async verifyCryptoSignature(data, signature, publicKey) {
197
- // In browser environment, use SubtleCrypto
198
- if (typeof globalThis.window !== 'undefined') {
199
- return this.verifyCryptoSignatureBrowser(data, signature, publicKey);
200
- }
201
- // In Node.js environment, use crypto module
202
- return this.verifyCryptoSignatureNode(data, signature, publicKey);
203
- }
204
- verifyCryptoSignatureNode(data, signature, publicKey) {
205
- if (!cryptoModule) {
206
- this.logger.error('Crypto module not available for signature verification');
207
- return false;
208
- }
209
- try {
210
- // Create verify object based on algorithm
211
- if (this.config.algorithm === 'ES256') {
212
- // ECDSA verification - requires lowercase 'sha256'
213
- const verify = cryptoModule.createVerify('sha256');
214
- verify.update(data);
215
- return verify.verify({
216
- key: publicKey,
217
- format: 'pem',
218
- type: 'spki',
219
- }, signature, 'base64');
220
- }
221
- else {
222
- // RSA verification (RS256)
223
- const verify = cryptoModule.createVerify('RSA-SHA256');
224
- verify.update(data);
225
- return verify.verify(publicKey, signature, 'base64');
226
- }
227
- }
228
- catch (error) {
229
- this.logger.error('Signature verification failed', error);
230
- return false;
231
- }
232
- }
233
- async verifyCryptoSignatureBrowser(_data, _signature, _publicKey) {
234
- // Browser implementation using SubtleCrypto
235
- // TODO: Implement SubtleCrypto-based verification
236
- this.logger.warn('Browser signature verification not yet implemented');
237
- return false;
238
- }
239
- validateConfig() {
240
- if (!this.config.trustedPublicKeys || this.config.trustedPublicKeys.size === 0) {
241
- this.logger.warn('No trusted public keys configured - all signatures will fail');
242
- }
243
- if (!this.config.algorithm) {
244
- throw new Error('Signature algorithm must be specified');
245
- }
246
- if (!['RS256', 'ES256'].includes(this.config.algorithm)) {
247
- throw new Error(`Unsupported algorithm: ${this.config.algorithm}`);
248
- }
249
- }
250
- }
@@ -1,115 +0,0 @@
1
- import type { SandboxConfig } from '@objectstack/spec/kernel';
2
- import type { ObjectLogger } from '../logger.js';
3
- /**
4
- * Resource Usage Statistics
5
- */
6
- export interface ResourceUsage {
7
- memory: {
8
- current: number;
9
- peak: number;
10
- limit?: number;
11
- };
12
- cpu: {
13
- current: number;
14
- average: number;
15
- limit?: number;
16
- };
17
- connections: {
18
- current: number;
19
- limit?: number;
20
- };
21
- }
22
- /**
23
- * Sandbox Execution Context
24
- * Represents an isolated execution environment for a plugin
25
- */
26
- export interface SandboxContext {
27
- pluginId: string;
28
- config: SandboxConfig;
29
- startTime: Date;
30
- resourceUsage: ResourceUsage;
31
- }
32
- /**
33
- * Plugin Sandbox Runtime
34
- *
35
- * Provides isolated execution environments for plugins with resource limits
36
- * and access controls
37
- */
38
- export declare class PluginSandboxRuntime {
39
- private logger;
40
- private sandboxes;
41
- private monitoringIntervals;
42
- constructor(logger: ObjectLogger);
43
- /**
44
- * Create a sandbox for a plugin
45
- */
46
- createSandbox(pluginId: string, config: SandboxConfig): SandboxContext;
47
- /**
48
- * Destroy a sandbox
49
- */
50
- destroySandbox(pluginId: string): void;
51
- /**
52
- * Check if resource access is allowed
53
- */
54
- checkResourceAccess(pluginId: string, resourceType: 'file' | 'network' | 'process' | 'env', resourcePath?: string): {
55
- allowed: boolean;
56
- reason?: string;
57
- };
58
- /**
59
- * Check file system access
60
- * WARNING: Uses simple prefix matching. For production, use proper path
61
- * resolution with path.resolve() and path.normalize() to prevent traversal.
62
- */
63
- private checkFileAccess;
64
- /**
65
- * Check network access
66
- * WARNING: Uses simple string matching. For production, use proper URL
67
- * parsing with new URL() and check hostname property.
68
- */
69
- private checkNetworkAccess;
70
- /**
71
- * Check process spawning access
72
- */
73
- private checkProcessAccess;
74
- /**
75
- * Check environment variable access
76
- */
77
- private checkEnvAccess;
78
- /**
79
- * Check resource limits
80
- */
81
- checkResourceLimits(pluginId: string): {
82
- withinLimits: boolean;
83
- violations: string[];
84
- };
85
- /**
86
- * Get resource usage for a plugin
87
- */
88
- getResourceUsage(pluginId: string): ResourceUsage | undefined;
89
- /**
90
- * Start monitoring resource usage
91
- */
92
- private startResourceMonitoring;
93
- /**
94
- * Stop monitoring resource usage
95
- */
96
- private stopResourceMonitoring;
97
- /**
98
- * Update resource usage statistics
99
- *
100
- * NOTE: Currently uses global process.memoryUsage() which tracks the entire
101
- * Node.js process, not individual plugins. For production, implement proper
102
- * per-plugin tracking using V8 heap snapshots or allocation tracking at
103
- * plugin boundaries.
104
- */
105
- private updateResourceUsage;
106
- /**
107
- * Get all active sandboxes
108
- */
109
- getAllSandboxes(): Map<string, SandboxContext>;
110
- /**
111
- * Shutdown sandbox runtime
112
- */
113
- shutdown(): void;
114
- }
115
- //# sourceMappingURL=sandbox-runtime.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sandbox-runtime.d.ts","sourceRoot":"","sources":["../../src/security/sandbox-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,GAAG,EAAE;QACH,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,WAAW,EAAE;QACX,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,aAAa,CAAC;IACtB,SAAS,EAAE,IAAI,CAAC;IAChB,aAAa,EAAE,aAAa,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,MAAM,CAAe;IAG7B,OAAO,CAAC,SAAS,CAAqC;IAGtD,OAAO,CAAC,mBAAmB,CAAqC;gBAEpD,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,cAAc;IA+BtE;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IActC;;OAEG;IACH,mBAAmB,CACjB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,EACpD,YAAY,CAAC,EAAE,MAAM,GACpB;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA0BxC;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAiDvB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAwD1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAkB1B;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG;QACrC,YAAY,EAAE,OAAO,CAAC;QACtB,UAAU,EAAE,MAAM,EAAE,CAAA;KACrB;IAiCD;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAK7D;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAQ9B;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAiC3B;;OAEG;IACH,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC;IAI9C;;OAEG;IACH,QAAQ,IAAI,IAAI;CAUjB"}
@@ -1,311 +0,0 @@
1
- import { getMemoryUsage } from '../utils/env.js';
2
- /**
3
- * Plugin Sandbox Runtime
4
- *
5
- * Provides isolated execution environments for plugins with resource limits
6
- * and access controls
7
- */
8
- export class PluginSandboxRuntime {
9
- constructor(logger) {
10
- // Active sandboxes (pluginId -> context)
11
- this.sandboxes = new Map();
12
- // Resource monitoring intervals
13
- this.monitoringIntervals = new Map();
14
- this.logger = logger.child({ component: 'SandboxRuntime' });
15
- }
16
- /**
17
- * Create a sandbox for a plugin
18
- */
19
- createSandbox(pluginId, config) {
20
- if (this.sandboxes.has(pluginId)) {
21
- throw new Error(`Sandbox already exists for plugin: ${pluginId}`);
22
- }
23
- const context = {
24
- pluginId,
25
- config,
26
- startTime: new Date(),
27
- resourceUsage: {
28
- memory: { current: 0, peak: 0, limit: config.memory?.maxHeap },
29
- cpu: { current: 0, average: 0, limit: config.cpu?.maxCpuPercent },
30
- connections: { current: 0, limit: config.network?.maxConnections },
31
- },
32
- };
33
- this.sandboxes.set(pluginId, context);
34
- // Start resource monitoring
35
- this.startResourceMonitoring(pluginId);
36
- this.logger.info('Sandbox created', {
37
- pluginId,
38
- level: config.level,
39
- memoryLimit: config.memory?.maxHeap,
40
- cpuLimit: config.cpu?.maxCpuPercent
41
- });
42
- return context;
43
- }
44
- /**
45
- * Destroy a sandbox
46
- */
47
- destroySandbox(pluginId) {
48
- const context = this.sandboxes.get(pluginId);
49
- if (!context) {
50
- return;
51
- }
52
- // Stop monitoring
53
- this.stopResourceMonitoring(pluginId);
54
- this.sandboxes.delete(pluginId);
55
- this.logger.info('Sandbox destroyed', { pluginId });
56
- }
57
- /**
58
- * Check if resource access is allowed
59
- */
60
- checkResourceAccess(pluginId, resourceType, resourcePath) {
61
- const context = this.sandboxes.get(pluginId);
62
- if (!context) {
63
- return { allowed: false, reason: 'Sandbox not found' };
64
- }
65
- const { config } = context;
66
- switch (resourceType) {
67
- case 'file':
68
- return this.checkFileAccess(config, resourcePath);
69
- case 'network':
70
- return this.checkNetworkAccess(config, resourcePath);
71
- case 'process':
72
- return this.checkProcessAccess(config);
73
- case 'env':
74
- return this.checkEnvAccess(config, resourcePath);
75
- default:
76
- return { allowed: false, reason: 'Unknown resource type' };
77
- }
78
- }
79
- /**
80
- * Check file system access
81
- * WARNING: Uses simple prefix matching. For production, use proper path
82
- * resolution with path.resolve() and path.normalize() to prevent traversal.
83
- */
84
- checkFileAccess(config, path) {
85
- if (config.level === 'none') {
86
- return { allowed: true };
87
- }
88
- if (!config.filesystem) {
89
- return { allowed: false, reason: 'File system access not configured' };
90
- }
91
- // If no path specified, check general access
92
- if (!path) {
93
- return { allowed: config.filesystem.mode !== 'none' };
94
- }
95
- // TODO: Use path.resolve() and path.normalize() for production
96
- // Check allowed paths
97
- const allowedPaths = config.filesystem.allowedPaths || [];
98
- const isAllowed = allowedPaths.some(allowed => {
99
- // Simple prefix matching - vulnerable to traversal attacks
100
- // TODO: Use proper path resolution
101
- return path.startsWith(allowed);
102
- });
103
- if (allowedPaths.length > 0 && !isAllowed) {
104
- return {
105
- allowed: false,
106
- reason: `Path not in allowed list: ${path}`
107
- };
108
- }
109
- // Check denied paths
110
- const deniedPaths = config.filesystem.deniedPaths || [];
111
- const isDenied = deniedPaths.some(denied => {
112
- return path.startsWith(denied);
113
- });
114
- if (isDenied) {
115
- return {
116
- allowed: false,
117
- reason: `Path is explicitly denied: ${path}`
118
- };
119
- }
120
- return { allowed: true };
121
- }
122
- /**
123
- * Check network access
124
- * WARNING: Uses simple string matching. For production, use proper URL
125
- * parsing with new URL() and check hostname property.
126
- */
127
- checkNetworkAccess(config, url) {
128
- if (config.level === 'none') {
129
- return { allowed: true };
130
- }
131
- if (!config.network) {
132
- return { allowed: false, reason: 'Network access not configured' };
133
- }
134
- // Check if network access is enabled
135
- if (config.network.mode === 'none') {
136
- return { allowed: false, reason: 'Network access disabled' };
137
- }
138
- // If no URL specified, check general access
139
- if (!url) {
140
- return { allowed: config.network.mode !== 'none' };
141
- }
142
- // TODO: Use new URL() and check hostname property for production
143
- // Check allowed hosts
144
- const allowedHosts = config.network.allowedHosts || [];
145
- if (allowedHosts.length > 0) {
146
- const isAllowed = allowedHosts.some(host => {
147
- // Simple string matching - vulnerable to bypass
148
- // TODO: Use proper URL parsing
149
- return url.includes(host);
150
- });
151
- if (!isAllowed) {
152
- return {
153
- allowed: false,
154
- reason: `Host not in allowed list: ${url}`
155
- };
156
- }
157
- }
158
- // Check denied hosts
159
- const deniedHosts = config.network.deniedHosts || [];
160
- const isDenied = deniedHosts.some(host => {
161
- return url.includes(host);
162
- });
163
- if (isDenied) {
164
- return {
165
- allowed: false,
166
- reason: `Host is blocked: ${url}`
167
- };
168
- }
169
- return { allowed: true };
170
- }
171
- /**
172
- * Check process spawning access
173
- */
174
- checkProcessAccess(config) {
175
- if (config.level === 'none') {
176
- return { allowed: true };
177
- }
178
- if (!config.process) {
179
- return { allowed: false, reason: 'Process access not configured' };
180
- }
181
- if (!config.process.allowSpawn) {
182
- return { allowed: false, reason: 'Process spawning not allowed' };
183
- }
184
- return { allowed: true };
185
- }
186
- /**
187
- * Check environment variable access
188
- */
189
- checkEnvAccess(config, varName) {
190
- if (config.level === 'none') {
191
- return { allowed: true };
192
- }
193
- if (!config.process) {
194
- return { allowed: false, reason: 'Environment access not configured' };
195
- }
196
- // If no variable specified, check general access
197
- if (!varName) {
198
- return { allowed: true };
199
- }
200
- // For now, allow all env access if process is configured
201
- // In a real implementation, would check specific allowed vars
202
- return { allowed: true };
203
- }
204
- /**
205
- * Check resource limits
206
- */
207
- checkResourceLimits(pluginId) {
208
- const context = this.sandboxes.get(pluginId);
209
- if (!context) {
210
- return { withinLimits: true, violations: [] };
211
- }
212
- const violations = [];
213
- const { resourceUsage, config } = context;
214
- // Check memory limit
215
- if (config.memory?.maxHeap &&
216
- resourceUsage.memory.current > config.memory.maxHeap) {
217
- violations.push(`Memory limit exceeded: ${resourceUsage.memory.current} > ${config.memory.maxHeap}`);
218
- }
219
- // Check CPU limit (would need runtime config)
220
- if (config.runtime?.resourceLimits?.maxCpu &&
221
- resourceUsage.cpu.current > config.runtime.resourceLimits.maxCpu) {
222
- violations.push(`CPU limit exceeded: ${resourceUsage.cpu.current}% > ${config.runtime.resourceLimits.maxCpu}%`);
223
- }
224
- // Check connection limit
225
- if (config.network?.maxConnections &&
226
- resourceUsage.connections.current > config.network.maxConnections) {
227
- violations.push(`Connection limit exceeded: ${resourceUsage.connections.current} > ${config.network.maxConnections}`);
228
- }
229
- return {
230
- withinLimits: violations.length === 0,
231
- violations,
232
- };
233
- }
234
- /**
235
- * Get resource usage for a plugin
236
- */
237
- getResourceUsage(pluginId) {
238
- const context = this.sandboxes.get(pluginId);
239
- return context?.resourceUsage;
240
- }
241
- /**
242
- * Start monitoring resource usage
243
- */
244
- startResourceMonitoring(pluginId) {
245
- // Monitor every 5 seconds
246
- const interval = setInterval(() => {
247
- this.updateResourceUsage(pluginId);
248
- }, 5000);
249
- this.monitoringIntervals.set(pluginId, interval);
250
- }
251
- /**
252
- * Stop monitoring resource usage
253
- */
254
- stopResourceMonitoring(pluginId) {
255
- const interval = this.monitoringIntervals.get(pluginId);
256
- if (interval) {
257
- clearInterval(interval);
258
- this.monitoringIntervals.delete(pluginId);
259
- }
260
- }
261
- /**
262
- * Update resource usage statistics
263
- *
264
- * NOTE: Currently uses global process.memoryUsage() which tracks the entire
265
- * Node.js process, not individual plugins. For production, implement proper
266
- * per-plugin tracking using V8 heap snapshots or allocation tracking at
267
- * plugin boundaries.
268
- */
269
- updateResourceUsage(pluginId) {
270
- const context = this.sandboxes.get(pluginId);
271
- if (!context) {
272
- return;
273
- }
274
- // In a real implementation, this would collect actual metrics
275
- // For now, this is a placeholder structure
276
- // Update memory usage (global process memory - not per-plugin)
277
- // TODO: Implement per-plugin memory tracking
278
- const memoryUsage = getMemoryUsage();
279
- context.resourceUsage.memory.current = memoryUsage.heapUsed;
280
- context.resourceUsage.memory.peak = Math.max(context.resourceUsage.memory.peak, memoryUsage.heapUsed);
281
- // Update CPU usage (would use process.cpuUsage() or similar)
282
- // This is a placeholder - real implementation would track per-plugin CPU
283
- // TODO: Implement per-plugin CPU tracking
284
- context.resourceUsage.cpu.current = 0;
285
- // Check for violations
286
- const { withinLimits, violations } = this.checkResourceLimits(pluginId);
287
- if (!withinLimits) {
288
- this.logger.warn('Resource limit violations detected', {
289
- pluginId,
290
- violations
291
- });
292
- }
293
- }
294
- /**
295
- * Get all active sandboxes
296
- */
297
- getAllSandboxes() {
298
- return new Map(this.sandboxes);
299
- }
300
- /**
301
- * Shutdown sandbox runtime
302
- */
303
- shutdown() {
304
- // Stop all monitoring
305
- for (const pluginId of this.monitoringIntervals.keys()) {
306
- this.stopResourceMonitoring(pluginId);
307
- }
308
- this.sandboxes.clear();
309
- this.logger.info('Sandbox runtime shutdown complete');
310
- }
311
- }