@objectstack/core 1.0.4 → 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.
- package/.turbo/turbo-build.log +58 -0
- package/CHANGELOG.md +12 -0
- package/dist/index.cjs +4294 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1777 -0
- package/dist/index.d.ts +1776 -21
- package/dist/index.js +4246 -23
- package/dist/index.js.map +1 -0
- package/package.json +4 -4
- package/tsconfig.json +1 -3
- package/dist/api-registry-plugin.d.ts +0 -54
- package/dist/api-registry-plugin.d.ts.map +0 -1
- package/dist/api-registry-plugin.js +0 -53
- package/dist/api-registry-plugin.test.d.ts +0 -2
- package/dist/api-registry-plugin.test.d.ts.map +0 -1
- package/dist/api-registry-plugin.test.js +0 -334
- package/dist/api-registry.d.ts +0 -259
- package/dist/api-registry.d.ts.map +0 -1
- package/dist/api-registry.js +0 -600
- package/dist/api-registry.test.d.ts +0 -2
- package/dist/api-registry.test.d.ts.map +0 -1
- package/dist/api-registry.test.js +0 -957
- package/dist/contracts/data-engine.d.ts +0 -62
- package/dist/contracts/data-engine.d.ts.map +0 -1
- package/dist/contracts/data-engine.js +0 -1
- package/dist/contracts/http-server.d.ts +0 -119
- package/dist/contracts/http-server.d.ts.map +0 -1
- package/dist/contracts/http-server.js +0 -11
- package/dist/contracts/logger.d.ts +0 -63
- package/dist/contracts/logger.d.ts.map +0 -1
- package/dist/contracts/logger.js +0 -1
- package/dist/dependency-resolver.d.ts +0 -62
- package/dist/dependency-resolver.d.ts.map +0 -1
- package/dist/dependency-resolver.js +0 -317
- package/dist/dependency-resolver.test.d.ts +0 -2
- package/dist/dependency-resolver.test.d.ts.map +0 -1
- package/dist/dependency-resolver.test.js +0 -241
- package/dist/health-monitor.d.ts +0 -65
- package/dist/health-monitor.d.ts.map +0 -1
- package/dist/health-monitor.js +0 -269
- package/dist/health-monitor.test.d.ts +0 -2
- package/dist/health-monitor.test.d.ts.map +0 -1
- package/dist/health-monitor.test.js +0 -68
- package/dist/hot-reload.d.ts +0 -79
- package/dist/hot-reload.d.ts.map +0 -1
- package/dist/hot-reload.js +0 -313
- package/dist/index.d.ts.map +0 -1
- package/dist/kernel-base.d.ts +0 -84
- package/dist/kernel-base.d.ts.map +0 -1
- package/dist/kernel-base.js +0 -219
- package/dist/kernel.d.ts +0 -113
- package/dist/kernel.d.ts.map +0 -1
- package/dist/kernel.js +0 -472
- package/dist/kernel.test.d.ts +0 -2
- package/dist/kernel.test.d.ts.map +0 -1
- package/dist/kernel.test.js +0 -414
- package/dist/lite-kernel.d.ts +0 -55
- package/dist/lite-kernel.d.ts.map +0 -1
- package/dist/lite-kernel.js +0 -112
- package/dist/lite-kernel.test.d.ts +0 -2
- package/dist/lite-kernel.test.d.ts.map +0 -1
- package/dist/lite-kernel.test.js +0 -161
- package/dist/logger.d.ts +0 -71
- package/dist/logger.d.ts.map +0 -1
- package/dist/logger.js +0 -312
- package/dist/logger.test.d.ts +0 -2
- package/dist/logger.test.d.ts.map +0 -1
- package/dist/logger.test.js +0 -92
- package/dist/plugin-loader.d.ts +0 -164
- package/dist/plugin-loader.d.ts.map +0 -1
- package/dist/plugin-loader.js +0 -319
- package/dist/plugin-loader.test.d.ts +0 -2
- package/dist/plugin-loader.test.d.ts.map +0 -1
- package/dist/plugin-loader.test.js +0 -348
- package/dist/qa/adapter.d.ts +0 -14
- package/dist/qa/adapter.d.ts.map +0 -1
- package/dist/qa/adapter.js +0 -1
- package/dist/qa/http-adapter.d.ts +0 -16
- package/dist/qa/http-adapter.d.ts.map +0 -1
- package/dist/qa/http-adapter.js +0 -107
- package/dist/qa/index.d.ts +0 -4
- package/dist/qa/index.d.ts.map +0 -1
- package/dist/qa/index.js +0 -3
- package/dist/qa/runner.d.ts +0 -27
- package/dist/qa/runner.d.ts.map +0 -1
- package/dist/qa/runner.js +0 -157
- package/dist/security/index.d.ts +0 -17
- package/dist/security/index.d.ts.map +0 -1
- package/dist/security/index.js +0 -17
- package/dist/security/permission-manager.d.ts +0 -96
- package/dist/security/permission-manager.d.ts.map +0 -1
- package/dist/security/permission-manager.js +0 -235
- package/dist/security/permission-manager.test.d.ts +0 -2
- package/dist/security/permission-manager.test.d.ts.map +0 -1
- package/dist/security/permission-manager.test.js +0 -220
- package/dist/security/plugin-config-validator.d.ts +0 -79
- package/dist/security/plugin-config-validator.d.ts.map +0 -1
- package/dist/security/plugin-config-validator.js +0 -166
- package/dist/security/plugin-config-validator.test.d.ts +0 -2
- package/dist/security/plugin-config-validator.test.d.ts.map +0 -1
- package/dist/security/plugin-config-validator.test.js +0 -223
- package/dist/security/plugin-permission-enforcer.d.ts +0 -154
- package/dist/security/plugin-permission-enforcer.d.ts.map +0 -1
- package/dist/security/plugin-permission-enforcer.js +0 -323
- package/dist/security/plugin-permission-enforcer.test.d.ts +0 -2
- package/dist/security/plugin-permission-enforcer.test.d.ts.map +0 -1
- package/dist/security/plugin-permission-enforcer.test.js +0 -205
- package/dist/security/plugin-signature-verifier.d.ts +0 -96
- package/dist/security/plugin-signature-verifier.d.ts.map +0 -1
- package/dist/security/plugin-signature-verifier.js +0 -250
- package/dist/security/sandbox-runtime.d.ts +0 -115
- package/dist/security/sandbox-runtime.d.ts.map +0 -1
- package/dist/security/sandbox-runtime.js +0 -311
- package/dist/security/security-scanner.d.ts +0 -92
- package/dist/security/security-scanner.d.ts.map +0 -1
- package/dist/security/security-scanner.js +0 -273
- package/dist/types.d.ts +0 -89
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
- package/dist/utils/env.d.ts +0 -20
- package/dist/utils/env.d.ts.map +0 -1
- package/dist/utils/env.js +0 -46
- package/dist/utils/env.test.d.ts +0 -2
- package/dist/utils/env.test.d.ts.map +0 -1
- 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
|
-
}
|