@objectstack/core 0.8.2 → 0.9.1

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 (73) hide show
  1. package/API_REGISTRY.md +392 -0
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +36 -0
  4. package/dist/api-registry-plugin.d.ts +54 -0
  5. package/dist/api-registry-plugin.d.ts.map +1 -0
  6. package/dist/api-registry-plugin.js +53 -0
  7. package/dist/api-registry-plugin.test.d.ts +2 -0
  8. package/dist/api-registry-plugin.test.d.ts.map +1 -0
  9. package/dist/api-registry-plugin.test.js +332 -0
  10. package/dist/api-registry.d.ts +259 -0
  11. package/dist/api-registry.d.ts.map +1 -0
  12. package/dist/api-registry.js +599 -0
  13. package/dist/api-registry.test.d.ts +2 -0
  14. package/dist/api-registry.test.d.ts.map +1 -0
  15. package/dist/api-registry.test.js +957 -0
  16. package/dist/index.d.ts +4 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +5 -0
  19. package/dist/logger.d.ts +1 -0
  20. package/dist/logger.d.ts.map +1 -1
  21. package/dist/logger.js +35 -11
  22. package/dist/plugin-loader.d.ts +3 -2
  23. package/dist/plugin-loader.d.ts.map +1 -1
  24. package/dist/plugin-loader.js +13 -11
  25. package/dist/qa/adapter.d.ts +14 -0
  26. package/dist/qa/adapter.d.ts.map +1 -0
  27. package/dist/qa/adapter.js +1 -0
  28. package/dist/qa/http-adapter.d.ts +16 -0
  29. package/dist/qa/http-adapter.d.ts.map +1 -0
  30. package/dist/qa/http-adapter.js +107 -0
  31. package/dist/qa/index.d.ts +4 -0
  32. package/dist/qa/index.d.ts.map +1 -0
  33. package/dist/qa/index.js +3 -0
  34. package/dist/qa/runner.d.ts +27 -0
  35. package/dist/qa/runner.d.ts.map +1 -0
  36. package/dist/qa/runner.js +157 -0
  37. package/dist/security/index.d.ts +14 -0
  38. package/dist/security/index.d.ts.map +1 -0
  39. package/dist/security/index.js +13 -0
  40. package/dist/security/plugin-config-validator.d.ts +79 -0
  41. package/dist/security/plugin-config-validator.d.ts.map +1 -0
  42. package/dist/security/plugin-config-validator.js +166 -0
  43. package/dist/security/plugin-config-validator.test.d.ts +2 -0
  44. package/dist/security/plugin-config-validator.test.d.ts.map +1 -0
  45. package/dist/security/plugin-config-validator.test.js +223 -0
  46. package/dist/security/plugin-permission-enforcer.d.ts +154 -0
  47. package/dist/security/plugin-permission-enforcer.d.ts.map +1 -0
  48. package/dist/security/plugin-permission-enforcer.js +323 -0
  49. package/dist/security/plugin-permission-enforcer.test.d.ts +2 -0
  50. package/dist/security/plugin-permission-enforcer.test.d.ts.map +1 -0
  51. package/dist/security/plugin-permission-enforcer.test.js +205 -0
  52. package/dist/security/plugin-signature-verifier.d.ts +96 -0
  53. package/dist/security/plugin-signature-verifier.d.ts.map +1 -0
  54. package/dist/security/plugin-signature-verifier.js +250 -0
  55. package/examples/api-registry-example.ts +557 -0
  56. package/package.json +2 -2
  57. package/src/api-registry-plugin.test.ts +391 -0
  58. package/src/api-registry-plugin.ts +86 -0
  59. package/src/api-registry.test.ts +1089 -0
  60. package/src/api-registry.ts +736 -0
  61. package/src/index.ts +6 -0
  62. package/src/logger.ts +36 -11
  63. package/src/plugin-loader.ts +17 -13
  64. package/src/qa/adapter.ts +14 -0
  65. package/src/qa/http-adapter.ts +114 -0
  66. package/src/qa/index.ts +3 -0
  67. package/src/qa/runner.ts +179 -0
  68. package/src/security/index.ts +29 -0
  69. package/src/security/plugin-config-validator.test.ts +276 -0
  70. package/src/security/plugin-config-validator.ts +191 -0
  71. package/src/security/plugin-permission-enforcer.test.ts +251 -0
  72. package/src/security/plugin-permission-enforcer.ts +408 -0
  73. package/src/security/plugin-signature-verifier.ts +359 -0
@@ -0,0 +1,250 @@
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
+ }