@objectstack/core 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/{ENHANCED_FEATURES.md → ADVANCED_FEATURES.md} +13 -13
  2. package/CHANGELOG.md +15 -0
  3. package/PHASE2_IMPLEMENTATION.md +388 -0
  4. package/README.md +60 -11
  5. package/REFACTORING_SUMMARY.md +40 -0
  6. package/dist/api-registry-plugin.test.js +20 -20
  7. package/dist/dependency-resolver.d.ts +62 -0
  8. package/dist/dependency-resolver.d.ts.map +1 -0
  9. package/dist/dependency-resolver.js +317 -0
  10. package/dist/dependency-resolver.test.d.ts +2 -0
  11. package/dist/dependency-resolver.test.d.ts.map +1 -0
  12. package/dist/dependency-resolver.test.js +241 -0
  13. package/dist/health-monitor.d.ts +65 -0
  14. package/dist/health-monitor.d.ts.map +1 -0
  15. package/dist/health-monitor.js +269 -0
  16. package/dist/health-monitor.test.d.ts +2 -0
  17. package/dist/health-monitor.test.d.ts.map +1 -0
  18. package/dist/health-monitor.test.js +68 -0
  19. package/dist/hot-reload.d.ts +79 -0
  20. package/dist/hot-reload.d.ts.map +1 -0
  21. package/dist/hot-reload.js +313 -0
  22. package/dist/index.d.ts +4 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +5 -1
  25. package/dist/kernel-base.d.ts +2 -2
  26. package/dist/kernel-base.js +2 -2
  27. package/dist/kernel.d.ts +79 -31
  28. package/dist/kernel.d.ts.map +1 -1
  29. package/dist/kernel.js +383 -73
  30. package/dist/kernel.test.js +373 -122
  31. package/dist/lite-kernel.d.ts +55 -0
  32. package/dist/lite-kernel.d.ts.map +1 -0
  33. package/dist/lite-kernel.js +112 -0
  34. package/dist/lite-kernel.test.d.ts +2 -0
  35. package/dist/lite-kernel.test.d.ts.map +1 -0
  36. package/dist/lite-kernel.test.js +161 -0
  37. package/dist/logger.d.ts +3 -2
  38. package/dist/logger.d.ts.map +1 -1
  39. package/dist/logger.js +61 -18
  40. package/dist/plugin-loader.d.ts +11 -0
  41. package/dist/plugin-loader.d.ts.map +1 -1
  42. package/dist/plugin-loader.js +34 -10
  43. package/dist/plugin-loader.test.js +9 -0
  44. package/dist/security/index.d.ts +3 -0
  45. package/dist/security/index.d.ts.map +1 -1
  46. package/dist/security/index.js +4 -0
  47. package/dist/security/permission-manager.d.ts +96 -0
  48. package/dist/security/permission-manager.d.ts.map +1 -0
  49. package/dist/security/permission-manager.js +235 -0
  50. package/dist/security/permission-manager.test.d.ts +2 -0
  51. package/dist/security/permission-manager.test.d.ts.map +1 -0
  52. package/dist/security/permission-manager.test.js +220 -0
  53. package/dist/security/plugin-signature-verifier.js +3 -3
  54. package/dist/security/sandbox-runtime.d.ts +115 -0
  55. package/dist/security/sandbox-runtime.d.ts.map +1 -0
  56. package/dist/security/sandbox-runtime.js +310 -0
  57. package/dist/security/security-scanner.d.ts +92 -0
  58. package/dist/security/security-scanner.d.ts.map +1 -0
  59. package/dist/security/security-scanner.js +273 -0
  60. package/examples/{enhanced-kernel-example.ts → kernel-features-example.ts} +6 -6
  61. package/examples/phase2-integration.ts +355 -0
  62. package/package.json +2 -2
  63. package/src/api-registry-plugin.test.ts +20 -20
  64. package/src/dependency-resolver.test.ts +287 -0
  65. package/src/dependency-resolver.ts +388 -0
  66. package/src/health-monitor.test.ts +81 -0
  67. package/src/health-monitor.ts +316 -0
  68. package/src/hot-reload.ts +388 -0
  69. package/src/index.ts +6 -1
  70. package/src/kernel-base.ts +2 -2
  71. package/src/kernel.test.ts +469 -134
  72. package/src/kernel.ts +464 -78
  73. package/src/lite-kernel.test.ts +200 -0
  74. package/src/lite-kernel.ts +135 -0
  75. package/src/logger.ts +64 -18
  76. package/src/plugin-loader.test.ts +10 -1
  77. package/src/plugin-loader.ts +42 -13
  78. package/src/security/index.ts +19 -0
  79. package/src/security/permission-manager.test.ts +256 -0
  80. package/src/security/permission-manager.ts +336 -0
  81. package/src/security/plugin-signature-verifier.ts +3 -3
  82. package/src/security/sandbox-runtime.ts +432 -0
  83. package/src/security/security-scanner.ts +365 -0
  84. package/dist/enhanced-kernel.d.ts +0 -103
  85. package/dist/enhanced-kernel.d.ts.map +0 -1
  86. package/dist/enhanced-kernel.js +0 -403
  87. package/dist/enhanced-kernel.test.d.ts +0 -2
  88. package/dist/enhanced-kernel.test.d.ts.map +0 -1
  89. package/dist/enhanced-kernel.test.js +0 -412
  90. package/src/enhanced-kernel.test.ts +0 -535
  91. package/src/enhanced-kernel.ts +0 -496
@@ -0,0 +1,96 @@
1
+ import type { Permission, PermissionSet, PermissionAction, ResourceType } from '@objectstack/spec/system';
2
+ import type { ObjectLogger } from '../logger.js';
3
+ /**
4
+ * Permission Grant
5
+ * Represents a granted permission at runtime
6
+ */
7
+ export interface PermissionGrant {
8
+ permissionId: string;
9
+ pluginId: string;
10
+ grantedAt: Date;
11
+ grantedBy?: string;
12
+ expiresAt?: Date;
13
+ conditions?: Record<string, any>;
14
+ }
15
+ /**
16
+ * Permission Check Result
17
+ */
18
+ export interface PermissionCheckResult {
19
+ allowed: boolean;
20
+ reason?: string;
21
+ requiredPermission?: string;
22
+ grantedPermissions?: string[];
23
+ }
24
+ /**
25
+ * Plugin Permission Manager
26
+ *
27
+ * Manages fine-grained permissions for plugin security and access control
28
+ */
29
+ export declare class PluginPermissionManager {
30
+ private logger;
31
+ private permissionSets;
32
+ private grants;
33
+ private grantDetails;
34
+ constructor(logger: ObjectLogger);
35
+ /**
36
+ * Register permission requirements for a plugin
37
+ */
38
+ registerPermissions(pluginId: string, permissionSet: PermissionSet): void;
39
+ /**
40
+ * Grant a permission to a plugin
41
+ */
42
+ grantPermission(pluginId: string, permissionId: string, grantedBy?: string, expiresAt?: Date): void;
43
+ /**
44
+ * Revoke a permission from a plugin
45
+ */
46
+ revokePermission(pluginId: string, permissionId: string): void;
47
+ /**
48
+ * Grant all permissions for a plugin
49
+ */
50
+ grantAllPermissions(pluginId: string, grantedBy?: string): void;
51
+ /**
52
+ * Check if a plugin has a specific permission
53
+ */
54
+ hasPermission(pluginId: string, permissionId: string): boolean;
55
+ /**
56
+ * Check if plugin can perform an action on a resource
57
+ */
58
+ checkAccess(pluginId: string, resource: ResourceType, action: PermissionAction, resourceId?: string): PermissionCheckResult;
59
+ /**
60
+ * Get all permissions for a plugin
61
+ */
62
+ getPluginPermissions(pluginId: string): Permission[];
63
+ /**
64
+ * Get granted permissions for a plugin
65
+ */
66
+ getGrantedPermissions(pluginId: string): string[];
67
+ /**
68
+ * Get required but not granted permissions
69
+ */
70
+ getMissingPermissions(pluginId: string): Permission[];
71
+ /**
72
+ * Check if all required permissions are granted
73
+ */
74
+ hasAllRequiredPermissions(pluginId: string): boolean;
75
+ /**
76
+ * Get permission grant details
77
+ */
78
+ getGrantDetails(pluginId: string, permissionId: string): PermissionGrant | undefined;
79
+ /**
80
+ * Validate permission against scope constraints
81
+ */
82
+ validatePermissionScope(permission: Permission, context: {
83
+ tenantId?: string;
84
+ userId?: string;
85
+ resourceId?: string;
86
+ }): boolean;
87
+ /**
88
+ * Clear all permissions for a plugin
89
+ */
90
+ clearPluginPermissions(pluginId: string): void;
91
+ /**
92
+ * Shutdown permission manager
93
+ */
94
+ shutdown(): void;
95
+ }
96
+ //# sourceMappingURL=permission-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permission-manager.d.ts","sourceRoot":"","sources":["../../src/security/permission-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,qBAAa,uBAAuB;IAClC,OAAO,CAAC,MAAM,CAAe;IAG7B,OAAO,CAAC,cAAc,CAAoC;IAG1D,OAAO,CAAC,MAAM,CAAkC;IAGhD,OAAO,CAAC,YAAY,CAAsC;gBAE9C,MAAM,EAAE,YAAY;IAIhC;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,GAAG,IAAI;IASzE;;OAEG;IACH,eAAe,CACb,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,IAAI,GACf,IAAI;IAmCP;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAY9D;;OAEG;IACH,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAa/D;;OAEG;IACH,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAsB9D;;OAEG;IACH,WAAW,CACT,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,gBAAgB,EACxB,UAAU,CAAC,EAAE,MAAM,GAClB,qBAAqB;IAyDxB;;OAEG;IACH,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAKpD;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE;IAKjD;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,EAAE;IAarD;;OAEG;IACH,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAIpD;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAKpF;;OAEG;IACH,uBAAuB,CACrB,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,GACA,OAAO;IAsBV;;OAEG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAe9C;;OAEG;IACH,QAAQ,IAAI,IAAI;CAOjB"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=permission-manager.test.d.ts.map
@@ -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
+ });
@@ -2,9 +2,9 @@
2
2
  let cryptoModule = null;
3
3
  if (typeof globalThis.window === 'undefined') {
4
4
  try {
5
- // Dynamic import for Node.js crypto module
6
- // eslint-disable-next-line @typescript-eslint/no-var-requires
7
- cryptoModule = require('crypto');
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
8
  }
9
9
  catch {
10
10
  // Crypto module not available (e.g., browser environment)
@@ -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