@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.
- package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
- package/CHANGELOG.md +7 -0
- package/PHASE2_IMPLEMENTATION.md +388 -0
- package/README.md +24 -11
- package/REFACTORING_SUMMARY.md +40 -0
- package/dist/api-registry-plugin.test.js +20 -20
- package/dist/dependency-resolver.d.ts +62 -0
- package/dist/dependency-resolver.d.ts.map +1 -0
- package/dist/dependency-resolver.js +317 -0
- package/dist/dependency-resolver.test.d.ts +2 -0
- package/dist/dependency-resolver.test.d.ts.map +1 -0
- package/dist/dependency-resolver.test.js +241 -0
- package/dist/health-monitor.d.ts +65 -0
- package/dist/health-monitor.d.ts.map +1 -0
- package/dist/health-monitor.js +269 -0
- package/dist/health-monitor.test.d.ts +2 -0
- package/dist/health-monitor.test.d.ts.map +1 -0
- package/dist/health-monitor.test.js +68 -0
- package/dist/hot-reload.d.ts +79 -0
- package/dist/hot-reload.d.ts.map +1 -0
- package/dist/hot-reload.js +313 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/kernel-base.d.ts +2 -2
- package/dist/kernel-base.js +2 -2
- package/dist/kernel.d.ts +79 -31
- package/dist/kernel.d.ts.map +1 -1
- package/dist/kernel.js +383 -73
- package/dist/kernel.test.js +373 -122
- package/dist/lite-kernel.d.ts +55 -0
- package/dist/lite-kernel.d.ts.map +1 -0
- package/dist/lite-kernel.js +112 -0
- package/dist/lite-kernel.test.d.ts +2 -0
- package/dist/lite-kernel.test.d.ts.map +1 -0
- package/dist/lite-kernel.test.js +161 -0
- package/dist/logger.d.ts +2 -2
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +26 -7
- package/dist/plugin-loader.d.ts +11 -0
- package/dist/plugin-loader.d.ts.map +1 -1
- package/dist/plugin-loader.js +34 -10
- package/dist/plugin-loader.test.js +9 -0
- package/dist/security/index.d.ts +3 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +4 -0
- package/dist/security/permission-manager.d.ts +96 -0
- package/dist/security/permission-manager.d.ts.map +1 -0
- package/dist/security/permission-manager.js +235 -0
- package/dist/security/permission-manager.test.d.ts +2 -0
- package/dist/security/permission-manager.test.d.ts.map +1 -0
- package/dist/security/permission-manager.test.js +220 -0
- package/dist/security/sandbox-runtime.d.ts +115 -0
- package/dist/security/sandbox-runtime.d.ts.map +1 -0
- package/dist/security/sandbox-runtime.js +310 -0
- package/dist/security/security-scanner.d.ts +92 -0
- package/dist/security/security-scanner.d.ts.map +1 -0
- package/dist/security/security-scanner.js +273 -0
- package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
- package/examples/phase2-integration.ts +355 -0
- package/package.json +2 -2
- package/src/api-registry-plugin.test.ts +20 -20
- package/src/dependency-resolver.test.ts +287 -0
- package/src/dependency-resolver.ts +388 -0
- package/src/health-monitor.test.ts +81 -0
- package/src/health-monitor.ts +316 -0
- package/src/hot-reload.ts +388 -0
- package/src/index.ts +6 -1
- package/src/kernel-base.ts +2 -2
- package/src/kernel.test.ts +469 -134
- package/src/kernel.ts +464 -78
- package/src/lite-kernel.test.ts +200 -0
- package/src/lite-kernel.ts +135 -0
- package/src/logger.ts +28 -7
- package/src/plugin-loader.test.ts +10 -1
- package/src/plugin-loader.ts +42 -13
- package/src/security/index.ts +19 -0
- package/src/security/permission-manager.test.ts +256 -0
- package/src/security/permission-manager.ts +336 -0
- package/src/security/sandbox-runtime.ts +432 -0
- package/src/security/security-scanner.ts +365 -0
- package/dist/enhanced-kernel.d.ts +0 -103
- package/dist/enhanced-kernel.d.ts.map +0 -1
- package/dist/enhanced-kernel.js +0 -403
- package/dist/enhanced-kernel.test.d.ts +0 -2
- package/dist/enhanced-kernel.test.d.ts.map +0 -1
- package/dist/enhanced-kernel.test.js +0 -412
- package/src/enhanced-kernel.test.ts +0 -535
- package/src/enhanced-kernel.ts +0 -496
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Permission Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages fine-grained permissions for plugin security and access control
|
|
5
|
+
*/
|
|
6
|
+
export class PluginPermissionManager {
|
|
7
|
+
constructor(logger) {
|
|
8
|
+
// Plugin permission definitions
|
|
9
|
+
this.permissionSets = new Map();
|
|
10
|
+
// Granted permissions (pluginId -> Set of permission IDs)
|
|
11
|
+
this.grants = new Map();
|
|
12
|
+
// Permission grant details
|
|
13
|
+
this.grantDetails = new Map();
|
|
14
|
+
this.logger = logger.child({ component: 'PermissionManager' });
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Register permission requirements for a plugin
|
|
18
|
+
*/
|
|
19
|
+
registerPermissions(pluginId, permissionSet) {
|
|
20
|
+
this.permissionSets.set(pluginId, permissionSet);
|
|
21
|
+
this.logger.info('Permissions registered for plugin', {
|
|
22
|
+
pluginId,
|
|
23
|
+
permissionCount: permissionSet.permissions.length
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Grant a permission to a plugin
|
|
28
|
+
*/
|
|
29
|
+
grantPermission(pluginId, permissionId, grantedBy, expiresAt) {
|
|
30
|
+
// Verify permission exists in plugin's declared permissions
|
|
31
|
+
const permissionSet = this.permissionSets.get(pluginId);
|
|
32
|
+
if (!permissionSet) {
|
|
33
|
+
throw new Error(`No permissions registered for plugin: ${pluginId}`);
|
|
34
|
+
}
|
|
35
|
+
const permission = permissionSet.permissions.find(p => p.id === permissionId);
|
|
36
|
+
if (!permission) {
|
|
37
|
+
throw new Error(`Permission ${permissionId} not declared by plugin ${pluginId}`);
|
|
38
|
+
}
|
|
39
|
+
// Create grant
|
|
40
|
+
if (!this.grants.has(pluginId)) {
|
|
41
|
+
this.grants.set(pluginId, new Set());
|
|
42
|
+
}
|
|
43
|
+
this.grants.get(pluginId).add(permissionId);
|
|
44
|
+
// Store grant details
|
|
45
|
+
const grantKey = `${pluginId}:${permissionId}`;
|
|
46
|
+
this.grantDetails.set(grantKey, {
|
|
47
|
+
permissionId,
|
|
48
|
+
pluginId,
|
|
49
|
+
grantedAt: new Date(),
|
|
50
|
+
grantedBy,
|
|
51
|
+
expiresAt,
|
|
52
|
+
});
|
|
53
|
+
this.logger.info('Permission granted', {
|
|
54
|
+
pluginId,
|
|
55
|
+
permissionId,
|
|
56
|
+
grantedBy
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Revoke a permission from a plugin
|
|
61
|
+
*/
|
|
62
|
+
revokePermission(pluginId, permissionId) {
|
|
63
|
+
const grants = this.grants.get(pluginId);
|
|
64
|
+
if (grants) {
|
|
65
|
+
grants.delete(permissionId);
|
|
66
|
+
const grantKey = `${pluginId}:${permissionId}`;
|
|
67
|
+
this.grantDetails.delete(grantKey);
|
|
68
|
+
this.logger.info('Permission revoked', { pluginId, permissionId });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Grant all permissions for a plugin
|
|
73
|
+
*/
|
|
74
|
+
grantAllPermissions(pluginId, grantedBy) {
|
|
75
|
+
const permissionSet = this.permissionSets.get(pluginId);
|
|
76
|
+
if (!permissionSet) {
|
|
77
|
+
throw new Error(`No permissions registered for plugin: ${pluginId}`);
|
|
78
|
+
}
|
|
79
|
+
for (const permission of permissionSet.permissions) {
|
|
80
|
+
this.grantPermission(pluginId, permission.id, grantedBy);
|
|
81
|
+
}
|
|
82
|
+
this.logger.info('All permissions granted', { pluginId, grantedBy });
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Check if a plugin has a specific permission
|
|
86
|
+
*/
|
|
87
|
+
hasPermission(pluginId, permissionId) {
|
|
88
|
+
const grants = this.grants.get(pluginId);
|
|
89
|
+
if (!grants) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
// Check if granted
|
|
93
|
+
if (!grants.has(permissionId)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
// Check expiration
|
|
97
|
+
const grantKey = `${pluginId}:${permissionId}`;
|
|
98
|
+
const grantDetails = this.grantDetails.get(grantKey);
|
|
99
|
+
if (grantDetails?.expiresAt && grantDetails.expiresAt < new Date()) {
|
|
100
|
+
this.revokePermission(pluginId, permissionId);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Check if plugin can perform an action on a resource
|
|
107
|
+
*/
|
|
108
|
+
checkAccess(pluginId, resource, action, resourceId) {
|
|
109
|
+
const permissionSet = this.permissionSets.get(pluginId);
|
|
110
|
+
if (!permissionSet) {
|
|
111
|
+
return {
|
|
112
|
+
allowed: false,
|
|
113
|
+
reason: 'No permissions registered for plugin',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Find matching permissions
|
|
117
|
+
const matchingPermissions = permissionSet.permissions.filter(p => {
|
|
118
|
+
// Check resource type
|
|
119
|
+
if (p.resource !== resource) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
// Check action
|
|
123
|
+
if (!p.actions.includes(action)) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
// Check resource filter if specified
|
|
127
|
+
if (resourceId && p.filter?.resourceIds) {
|
|
128
|
+
if (!p.filter.resourceIds.includes(resourceId)) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
});
|
|
134
|
+
if (matchingPermissions.length === 0) {
|
|
135
|
+
return {
|
|
136
|
+
allowed: false,
|
|
137
|
+
reason: `No permission found for ${action} on ${resource}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Check if any matching permission is granted
|
|
141
|
+
const grantedPermissions = matchingPermissions.filter(p => this.hasPermission(pluginId, p.id));
|
|
142
|
+
if (grantedPermissions.length === 0) {
|
|
143
|
+
return {
|
|
144
|
+
allowed: false,
|
|
145
|
+
reason: 'Required permissions not granted',
|
|
146
|
+
requiredPermission: matchingPermissions[0].id,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
allowed: true,
|
|
151
|
+
grantedPermissions: grantedPermissions.map(p => p.id),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get all permissions for a plugin
|
|
156
|
+
*/
|
|
157
|
+
getPluginPermissions(pluginId) {
|
|
158
|
+
const permissionSet = this.permissionSets.get(pluginId);
|
|
159
|
+
return permissionSet?.permissions || [];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get granted permissions for a plugin
|
|
163
|
+
*/
|
|
164
|
+
getGrantedPermissions(pluginId) {
|
|
165
|
+
const grants = this.grants.get(pluginId);
|
|
166
|
+
return grants ? Array.from(grants) : [];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get required but not granted permissions
|
|
170
|
+
*/
|
|
171
|
+
getMissingPermissions(pluginId) {
|
|
172
|
+
const permissionSet = this.permissionSets.get(pluginId);
|
|
173
|
+
if (!permissionSet) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const granted = this.grants.get(pluginId) || new Set();
|
|
177
|
+
return permissionSet.permissions.filter(p => p.required && !granted.has(p.id));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Check if all required permissions are granted
|
|
181
|
+
*/
|
|
182
|
+
hasAllRequiredPermissions(pluginId) {
|
|
183
|
+
return this.getMissingPermissions(pluginId).length === 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get permission grant details
|
|
187
|
+
*/
|
|
188
|
+
getGrantDetails(pluginId, permissionId) {
|
|
189
|
+
const grantKey = `${pluginId}:${permissionId}`;
|
|
190
|
+
return this.grantDetails.get(grantKey);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validate permission against scope constraints
|
|
194
|
+
*/
|
|
195
|
+
validatePermissionScope(permission, context) {
|
|
196
|
+
switch (permission.scope) {
|
|
197
|
+
case 'global':
|
|
198
|
+
return true;
|
|
199
|
+
case 'tenant':
|
|
200
|
+
return !!context.tenantId;
|
|
201
|
+
case 'user':
|
|
202
|
+
return !!context.userId;
|
|
203
|
+
case 'resource':
|
|
204
|
+
return !!context.resourceId;
|
|
205
|
+
case 'plugin':
|
|
206
|
+
return true;
|
|
207
|
+
default:
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Clear all permissions for a plugin
|
|
213
|
+
*/
|
|
214
|
+
clearPluginPermissions(pluginId) {
|
|
215
|
+
this.permissionSets.delete(pluginId);
|
|
216
|
+
const grants = this.grants.get(pluginId);
|
|
217
|
+
if (grants) {
|
|
218
|
+
for (const permissionId of grants) {
|
|
219
|
+
const grantKey = `${pluginId}:${permissionId}`;
|
|
220
|
+
this.grantDetails.delete(grantKey);
|
|
221
|
+
}
|
|
222
|
+
this.grants.delete(pluginId);
|
|
223
|
+
}
|
|
224
|
+
this.logger.info('All permissions cleared', { pluginId });
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Shutdown permission manager
|
|
228
|
+
*/
|
|
229
|
+
shutdown() {
|
|
230
|
+
this.permissionSets.clear();
|
|
231
|
+
this.grants.clear();
|
|
232
|
+
this.grantDetails.clear();
|
|
233
|
+
this.logger.info('Permission manager shutdown complete');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-manager.test.d.ts","sourceRoot":"","sources":["../../src/security/permission-manager.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { PluginPermissionManager } from './permission-manager.js';
|
|
3
|
+
import { createLogger } from '../logger.js';
|
|
4
|
+
describe('PluginPermissionManager', () => {
|
|
5
|
+
let manager;
|
|
6
|
+
let logger;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
logger = createLogger({ level: 'silent' });
|
|
9
|
+
manager = new PluginPermissionManager(logger);
|
|
10
|
+
});
|
|
11
|
+
describe('registerPermissions', () => {
|
|
12
|
+
it('should register permissions for a plugin', () => {
|
|
13
|
+
const permissionSet = {
|
|
14
|
+
permissions: [
|
|
15
|
+
{
|
|
16
|
+
id: 'read-data',
|
|
17
|
+
resource: 'data.object',
|
|
18
|
+
actions: ['read'],
|
|
19
|
+
scope: 'plugin',
|
|
20
|
+
description: 'Read object data',
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
defaultGrant: 'prompt',
|
|
25
|
+
};
|
|
26
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
27
|
+
const permissions = manager.getPluginPermissions('test-plugin');
|
|
28
|
+
expect(permissions).toHaveLength(1);
|
|
29
|
+
expect(permissions[0].id).toBe('read-data');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('grantPermission', () => {
|
|
33
|
+
it('should grant a permission to a plugin', () => {
|
|
34
|
+
const permissionSet = {
|
|
35
|
+
permissions: [
|
|
36
|
+
{
|
|
37
|
+
id: 'read-data',
|
|
38
|
+
resource: 'data.object',
|
|
39
|
+
actions: ['read'],
|
|
40
|
+
scope: 'plugin',
|
|
41
|
+
description: 'Read object data',
|
|
42
|
+
required: true,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
defaultGrant: 'prompt',
|
|
46
|
+
};
|
|
47
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
48
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
49
|
+
expect(manager.hasPermission('test-plugin', 'read-data')).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it('should throw error for non-existent permission', () => {
|
|
52
|
+
const permissionSet = {
|
|
53
|
+
permissions: [],
|
|
54
|
+
defaultGrant: 'prompt',
|
|
55
|
+
};
|
|
56
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
57
|
+
expect(() => {
|
|
58
|
+
manager.grantPermission('test-plugin', 'invalid-permission');
|
|
59
|
+
}).toThrow();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('revokePermission', () => {
|
|
63
|
+
it('should revoke a permission from a plugin', () => {
|
|
64
|
+
const permissionSet = {
|
|
65
|
+
permissions: [
|
|
66
|
+
{
|
|
67
|
+
id: 'read-data',
|
|
68
|
+
resource: 'data.object',
|
|
69
|
+
actions: ['read'],
|
|
70
|
+
scope: 'plugin',
|
|
71
|
+
description: 'Read object data',
|
|
72
|
+
required: true,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
defaultGrant: 'prompt',
|
|
76
|
+
};
|
|
77
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
78
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
79
|
+
manager.revokePermission('test-plugin', 'read-data');
|
|
80
|
+
expect(manager.hasPermission('test-plugin', 'read-data')).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('checkAccess', () => {
|
|
84
|
+
it('should allow access when permission is granted', () => {
|
|
85
|
+
const permissionSet = {
|
|
86
|
+
permissions: [
|
|
87
|
+
{
|
|
88
|
+
id: 'read-data',
|
|
89
|
+
resource: 'data.object',
|
|
90
|
+
actions: ['read'],
|
|
91
|
+
scope: 'plugin',
|
|
92
|
+
description: 'Read object data',
|
|
93
|
+
required: true,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
defaultGrant: 'prompt',
|
|
97
|
+
};
|
|
98
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
99
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
100
|
+
const result = manager.checkAccess('test-plugin', 'data.object', 'read');
|
|
101
|
+
expect(result.allowed).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it('should deny access when permission is not granted', () => {
|
|
104
|
+
const permissionSet = {
|
|
105
|
+
permissions: [
|
|
106
|
+
{
|
|
107
|
+
id: 'read-data',
|
|
108
|
+
resource: 'data.object',
|
|
109
|
+
actions: ['read'],
|
|
110
|
+
scope: 'plugin',
|
|
111
|
+
description: 'Read object data',
|
|
112
|
+
required: true,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
defaultGrant: 'prompt',
|
|
116
|
+
};
|
|
117
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
118
|
+
const result = manager.checkAccess('test-plugin', 'data.object', 'read');
|
|
119
|
+
expect(result.allowed).toBe(false);
|
|
120
|
+
});
|
|
121
|
+
it('should deny access when permission does not exist', () => {
|
|
122
|
+
const permissionSet = {
|
|
123
|
+
permissions: [],
|
|
124
|
+
defaultGrant: 'prompt',
|
|
125
|
+
};
|
|
126
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
127
|
+
const result = manager.checkAccess('test-plugin', 'data.object', 'read');
|
|
128
|
+
expect(result.allowed).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('getMissingPermissions', () => {
|
|
132
|
+
it('should return missing required permissions', () => {
|
|
133
|
+
const permissionSet = {
|
|
134
|
+
permissions: [
|
|
135
|
+
{
|
|
136
|
+
id: 'read-data',
|
|
137
|
+
resource: 'data.object',
|
|
138
|
+
actions: ['read'],
|
|
139
|
+
scope: 'plugin',
|
|
140
|
+
description: 'Read object data',
|
|
141
|
+
required: true,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'write-data',
|
|
145
|
+
resource: 'data.object',
|
|
146
|
+
actions: ['create', 'update'],
|
|
147
|
+
scope: 'plugin',
|
|
148
|
+
description: 'Write object data',
|
|
149
|
+
required: true,
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
defaultGrant: 'prompt',
|
|
153
|
+
};
|
|
154
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
155
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
156
|
+
const missing = manager.getMissingPermissions('test-plugin');
|
|
157
|
+
expect(missing).toHaveLength(1);
|
|
158
|
+
expect(missing[0].id).toBe('write-data');
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('hasAllRequiredPermissions', () => {
|
|
162
|
+
it('should return true when all required permissions are granted', () => {
|
|
163
|
+
const permissionSet = {
|
|
164
|
+
permissions: [
|
|
165
|
+
{
|
|
166
|
+
id: 'read-data',
|
|
167
|
+
resource: 'data.object',
|
|
168
|
+
actions: ['read'],
|
|
169
|
+
scope: 'plugin',
|
|
170
|
+
description: 'Read object data',
|
|
171
|
+
required: true,
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
defaultGrant: 'prompt',
|
|
175
|
+
};
|
|
176
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
177
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
178
|
+
expect(manager.hasAllRequiredPermissions('test-plugin')).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it('should return false when required permissions are missing', () => {
|
|
181
|
+
const permissionSet = {
|
|
182
|
+
permissions: [
|
|
183
|
+
{
|
|
184
|
+
id: 'read-data',
|
|
185
|
+
resource: 'data.object',
|
|
186
|
+
actions: ['read'],
|
|
187
|
+
scope: 'plugin',
|
|
188
|
+
description: 'Read object data',
|
|
189
|
+
required: true,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
defaultGrant: 'prompt',
|
|
193
|
+
};
|
|
194
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
195
|
+
expect(manager.hasAllRequiredPermissions('test-plugin')).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('clearPluginPermissions', () => {
|
|
199
|
+
it('should clear all permissions for a plugin', () => {
|
|
200
|
+
const permissionSet = {
|
|
201
|
+
permissions: [
|
|
202
|
+
{
|
|
203
|
+
id: 'read-data',
|
|
204
|
+
resource: 'data.object',
|
|
205
|
+
actions: ['read'],
|
|
206
|
+
scope: 'plugin',
|
|
207
|
+
description: 'Read object data',
|
|
208
|
+
required: true,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
defaultGrant: 'prompt',
|
|
212
|
+
};
|
|
213
|
+
manager.registerPermissions('test-plugin', permissionSet);
|
|
214
|
+
manager.grantPermission('test-plugin', 'read-data');
|
|
215
|
+
manager.clearPluginPermissions('test-plugin');
|
|
216
|
+
expect(manager.getPluginPermissions('test-plugin')).toHaveLength(0);
|
|
217
|
+
expect(manager.getGrantedPermissions('test-plugin')).toHaveLength(0);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { SandboxConfig } from '@objectstack/spec/system';
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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;AAEjD;;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"}
|