@enactprotocol/shared 1.0.12

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 (97) hide show
  1. package/dist/LocalToolResolver.d.ts +84 -0
  2. package/dist/LocalToolResolver.js +353 -0
  3. package/dist/api/enact-api.d.ts +124 -0
  4. package/dist/api/enact-api.js +406 -0
  5. package/dist/api/index.d.ts +2 -0
  6. package/dist/api/index.js +2 -0
  7. package/dist/api/types.d.ts +83 -0
  8. package/dist/api/types.js +1 -0
  9. package/dist/core/DaggerExecutionProvider.d.ts +169 -0
  10. package/dist/core/DaggerExecutionProvider.js +996 -0
  11. package/dist/core/DirectExecutionProvider.d.ts +23 -0
  12. package/dist/core/DirectExecutionProvider.js +406 -0
  13. package/dist/core/EnactCore.d.ts +138 -0
  14. package/dist/core/EnactCore.js +609 -0
  15. package/dist/core/index.d.ts +3 -0
  16. package/dist/core/index.js +3 -0
  17. package/dist/exec/index.d.ts +3 -0
  18. package/dist/exec/index.js +3 -0
  19. package/dist/exec/logger.d.ts +11 -0
  20. package/dist/exec/logger.js +57 -0
  21. package/dist/exec/validate.d.ts +5 -0
  22. package/dist/exec/validate.js +167 -0
  23. package/dist/index.d.ts +25 -0
  24. package/dist/index.js +29 -0
  25. package/dist/lib/enact-direct.d.ts +156 -0
  26. package/dist/lib/enact-direct.js +158 -0
  27. package/dist/lib/index.d.ts +1 -0
  28. package/dist/lib/index.js +1 -0
  29. package/dist/security/index.d.ts +3 -0
  30. package/dist/security/index.js +3 -0
  31. package/dist/security/security.d.ts +23 -0
  32. package/dist/security/security.js +137 -0
  33. package/dist/security/sign.d.ts +103 -0
  34. package/dist/security/sign.js +532 -0
  35. package/dist/security/verification-enforcer.d.ts +41 -0
  36. package/dist/security/verification-enforcer.js +181 -0
  37. package/dist/services/McpCoreService.d.ts +102 -0
  38. package/dist/services/McpCoreService.js +120 -0
  39. package/dist/services/index.d.ts +1 -0
  40. package/dist/services/index.js +1 -0
  41. package/dist/types.d.ts +130 -0
  42. package/dist/types.js +3 -0
  43. package/dist/utils/config.d.ts +32 -0
  44. package/dist/utils/config.js +78 -0
  45. package/dist/utils/env-loader.d.ts +54 -0
  46. package/dist/utils/env-loader.js +270 -0
  47. package/dist/utils/help.d.ts +36 -0
  48. package/dist/utils/help.js +248 -0
  49. package/dist/utils/index.d.ts +7 -0
  50. package/dist/utils/index.js +7 -0
  51. package/dist/utils/logger.d.ts +35 -0
  52. package/dist/utils/logger.js +75 -0
  53. package/dist/utils/silent-monitor.d.ts +67 -0
  54. package/dist/utils/silent-monitor.js +242 -0
  55. package/dist/utils/timeout.d.ts +5 -0
  56. package/dist/utils/timeout.js +23 -0
  57. package/dist/utils/version.d.ts +4 -0
  58. package/dist/utils/version.js +14 -0
  59. package/dist/web/env-manager-server.d.ts +29 -0
  60. package/dist/web/env-manager-server.js +367 -0
  61. package/dist/web/index.d.ts +1 -0
  62. package/dist/web/index.js +1 -0
  63. package/package.json +79 -0
  64. package/src/LocalToolResolver.ts +424 -0
  65. package/src/api/enact-api.ts +569 -0
  66. package/src/api/index.ts +2 -0
  67. package/src/api/types.ts +93 -0
  68. package/src/core/DaggerExecutionProvider.ts +1308 -0
  69. package/src/core/DirectExecutionProvider.ts +484 -0
  70. package/src/core/EnactCore.ts +833 -0
  71. package/src/core/index.ts +3 -0
  72. package/src/exec/index.ts +3 -0
  73. package/src/exec/logger.ts +63 -0
  74. package/src/exec/validate.ts +238 -0
  75. package/src/index.ts +42 -0
  76. package/src/lib/enact-direct.ts +258 -0
  77. package/src/lib/index.ts +1 -0
  78. package/src/security/index.ts +3 -0
  79. package/src/security/security.ts +188 -0
  80. package/src/security/sign.ts +797 -0
  81. package/src/security/verification-enforcer.ts +268 -0
  82. package/src/services/McpCoreService.ts +203 -0
  83. package/src/services/index.ts +1 -0
  84. package/src/types.ts +190 -0
  85. package/src/utils/config.ts +97 -0
  86. package/src/utils/env-loader.ts +370 -0
  87. package/src/utils/help.ts +257 -0
  88. package/src/utils/index.ts +7 -0
  89. package/src/utils/logger.ts +83 -0
  90. package/src/utils/silent-monitor.ts +328 -0
  91. package/src/utils/timeout.ts +26 -0
  92. package/src/utils/version.ts +16 -0
  93. package/src/web/env-manager-server.ts +465 -0
  94. package/src/web/index.ts +1 -0
  95. package/src/web/static/app.js +663 -0
  96. package/src/web/static/index.html +117 -0
  97. package/src/web/static/style.css +291 -0
@@ -0,0 +1,532 @@
1
+ // enact-signer.ts - Exact webapp compatibility
2
+ import * as crypto from "crypto";
3
+ import { parse, stringify } from "yaml";
4
+ import * as fs from "fs";
5
+ import * as path from "path";
6
+ /**
7
+ * EXACT copy of webapp's createCanonicalToolDefinition
8
+ * This MUST match the webapp's cryptoService function exactly
9
+ */
10
+ function createCanonicalToolDefinition(tool) {
11
+ const canonical = {};
12
+ // CRITICAL: These must be in the exact same order as the webapp
13
+ const orderedFields = [
14
+ "name",
15
+ "description",
16
+ "command",
17
+ "protocol_version",
18
+ "version",
19
+ "timeout",
20
+ "tags",
21
+ "input_schema",
22
+ "output_schema",
23
+ "annotations",
24
+ "env_vars",
25
+ "examples",
26
+ "resources",
27
+ "doc",
28
+ "authors",
29
+ "enact",
30
+ ];
31
+ // Add fields in the specific order
32
+ for (const field of orderedFields) {
33
+ if (tool[field] !== undefined) {
34
+ canonical[field] = tool[field];
35
+ }
36
+ }
37
+ // Add any remaining fields not in the ordered list (sorted)
38
+ const remainingFields = Object.keys(tool)
39
+ .filter((key) => !orderedFields.includes(key))
40
+ .sort();
41
+ for (const field of remainingFields) {
42
+ if (tool[field] !== undefined) {
43
+ canonical[field] = tool[field];
44
+ }
45
+ }
46
+ return canonical;
47
+ }
48
+ /**
49
+ * Create canonical tool JSON EXACTLY like the webapp does
50
+ * This mirrors the webapp's createCanonicalToolJson function
51
+ */
52
+ function createCanonicalToolJson(toolData) {
53
+ // Convert Tool to the format expected by createCanonicalToolDefinition
54
+ // CRITICAL: Use the exact same field mapping as the webapp
55
+ const toolRecord = {
56
+ name: toolData.name,
57
+ description: toolData.description,
58
+ command: toolData.command,
59
+ // Map database fields to canonical fields (EXACT webapp mapping)
60
+ protocol_version: toolData.protocol_version,
61
+ version: toolData.version,
62
+ timeout: toolData.timeout,
63
+ tags: toolData.tags,
64
+ // Handle schema field mappings (use underscore versions like webapp)
65
+ input_schema: toolData.input_schema, // NOT inputSchema
66
+ output_schema: toolData.output_schema, // NOT outputSchema
67
+ annotations: toolData.annotations,
68
+ env_vars: toolData.env_vars, // NOT env
69
+ examples: toolData.examples,
70
+ resources: toolData.resources,
71
+ doc: toolData.doc, // Use direct field, not from raw_content
72
+ authors: toolData.authors, // Use direct field, not from raw_content
73
+ // Add enact field if missing (webapp behavior)
74
+ enact: toolData.enact || "1.0.0",
75
+ };
76
+ // Use the standardized canonical function from cryptoService
77
+ const canonical = createCanonicalToolDefinition(toolRecord);
78
+ // Return deterministic JSON with sorted keys EXACTLY like webapp
79
+ return JSON.stringify(canonical, Object.keys(canonical).sort());
80
+ }
81
+ const DEFAULT_POLICY = {
82
+ minimumSignatures: 1,
83
+ allowedAlgorithms: ["sha256"],
84
+ };
85
+ // Default directory for trusted keys
86
+ const TRUSTED_KEYS_DIR = path.join(process.env.HOME || ".", ".enact", "trusted-keys");
87
+ /**
88
+ * Get all trusted public keys mapped by their base64 representation
89
+ * @returns Map of base64 public key -> PEM content
90
+ */
91
+ export function getTrustedPublicKeysMap() {
92
+ const trustedKeys = new Map();
93
+ // Load keys from the filesystem
94
+ if (fs.existsSync(TRUSTED_KEYS_DIR)) {
95
+ try {
96
+ const files = fs.readdirSync(TRUSTED_KEYS_DIR);
97
+ for (const file of files) {
98
+ if (file.endsWith(".pem")) {
99
+ const keyPath = path.join(TRUSTED_KEYS_DIR, file);
100
+ const pemContent = fs.readFileSync(keyPath, "utf8");
101
+ // Convert PEM to base64 for map key
102
+ const base64Key = pemToBase64(pemContent);
103
+ trustedKeys.set(base64Key, pemContent);
104
+ }
105
+ }
106
+ }
107
+ catch (error) {
108
+ console.error(`Error reading trusted keys: ${error.message}`);
109
+ }
110
+ }
111
+ return trustedKeys;
112
+ }
113
+ /**
114
+ * Convert PEM public key to base64 format for use as map key
115
+ */
116
+ function pemToBase64(pem) {
117
+ return pem
118
+ .replace(/-----BEGIN PUBLIC KEY-----/, "")
119
+ .replace(/-----END PUBLIC KEY-----/, "")
120
+ .replace(/\s/g, "");
121
+ }
122
+ /**
123
+ * Convert base64 key back to PEM format
124
+ */
125
+ function base64ToPem(base64) {
126
+ return `-----BEGIN PUBLIC KEY-----\n${base64.match(/.{1,64}/g)?.join("\n")}\n-----END PUBLIC KEY-----`;
127
+ }
128
+ /**
129
+ * Sign an Enact tool and add to the signatures map
130
+ * Uses EXACT same process as webapp for perfect compatibility
131
+ */
132
+ export async function signTool(toolPath, privateKeyPath, publicKeyPath, signerInfo, outputPath) {
133
+ // Read files
134
+ const toolYaml = fs.readFileSync(toolPath, "utf8");
135
+ const privateKey = fs.readFileSync(privateKeyPath, "utf8");
136
+ const publicKeyPem = fs.readFileSync(publicKeyPath, "utf8");
137
+ // Parse the YAML
138
+ const tool = parse(toolYaml);
139
+ // Create a copy for signing (without signatures)
140
+ const toolForSigning = { ...tool };
141
+ delete toolForSigning.signatures;
142
+ // Use EXACT same canonical JSON creation as webapp
143
+ const canonicalJson = createCanonicalToolJson(toolForSigning);
144
+ console.error("=== SIGNING DEBUG (WEBAPP COMPATIBLE) ===");
145
+ console.error("Tool for signing:", JSON.stringify(toolForSigning, null, 2));
146
+ console.error("Canonical JSON (webapp format):", canonicalJson);
147
+ console.error("Canonical JSON length:", canonicalJson.length);
148
+ console.error("==========================================");
149
+ // Create tool hash exactly like webapp (SHA-256 hash of canonical JSON)
150
+ const toolHashBytes = await hashTool(toolForSigning);
151
+ // Sign using Web Crypto API to match webapp exactly
152
+ const { webcrypto } = await import("node:crypto");
153
+ // Import the private key for Web Crypto API
154
+ const privateKeyData = crypto
155
+ .createPrivateKey({
156
+ key: privateKey,
157
+ format: "pem",
158
+ type: "pkcs8",
159
+ })
160
+ .export({ format: "der", type: "pkcs8" });
161
+ const privateKeyObj = await webcrypto.subtle.importKey("pkcs8", privateKeyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["sign"]);
162
+ // Sign the hash bytes using Web Crypto API (produces IEEE P1363 format)
163
+ const signatureArrayBuffer = await webcrypto.subtle.sign({ name: "ECDSA", hash: { name: "SHA-256" } }, privateKeyObj, toolHashBytes);
164
+ const signature = new Uint8Array(signatureArrayBuffer);
165
+ const signatureB64 = Buffer.from(signature).toString("base64");
166
+ console.error("Generated signature (Web Crypto API):", signatureB64);
167
+ console.error("Signature length:", signature.length, "bytes (should be 64 for P-256)");
168
+ // Convert public key to base64 for map key
169
+ const publicKeyBase64 = pemToBase64(publicKeyPem);
170
+ // Initialize signatures object if it doesn't exist
171
+ if (!tool.signatures) {
172
+ tool.signatures = {};
173
+ }
174
+ // Add signature to the map using public key as key
175
+ tool.signatures[publicKeyBase64] = {
176
+ algorithm: "sha256",
177
+ type: "ecdsa-p256",
178
+ signer: signerInfo.id,
179
+ created: new Date().toISOString(),
180
+ value: signatureB64,
181
+ ...(signerInfo.role && { role: signerInfo.role }),
182
+ };
183
+ // Convert back to YAML
184
+ const signedToolYaml = stringify(tool);
185
+ // Write to output file if specified
186
+ if (outputPath) {
187
+ fs.writeFileSync(outputPath, signedToolYaml);
188
+ }
189
+ return signedToolYaml;
190
+ }
191
+ /**
192
+ * Hash tool data for signing - EXACT copy of webapp's hashTool function
193
+ */
194
+ async function hashTool(tool) {
195
+ // Create canonical representation
196
+ const canonical = createCanonicalToolDefinition(tool);
197
+ // Remove signature if present to avoid circular dependency
198
+ const { signature, ...toolForSigning } = canonical;
199
+ // Create deterministic JSON with sorted keys
200
+ const canonicalJson = JSON.stringify(toolForSigning, Object.keys(toolForSigning).sort());
201
+ console.error("🔍 Canonical JSON for hashing:", canonicalJson);
202
+ console.error("🔍 Canonical JSON length:", canonicalJson.length);
203
+ // Hash the canonical JSON
204
+ const encoder = new TextEncoder();
205
+ const data = encoder.encode(canonicalJson);
206
+ // Use Web Crypto API for hashing to match webapp exactly
207
+ const { webcrypto } = await import("node:crypto");
208
+ const hashBuffer = await webcrypto.subtle.digest("SHA-256", data);
209
+ const hashBytes = new Uint8Array(hashBuffer);
210
+ console.error("🔍 SHA-256 hash length:", hashBytes.length, "bytes (should be 32)");
211
+ return hashBytes;
212
+ }
213
+ /**
214
+ * Verify tool signature using EXACT same process as webapp
215
+ * This mirrors the webapp's verifyToolSignature function exactly
216
+ */
217
+ export async function verifyToolSignature(toolObject, signatureB64, publicKeyObj) {
218
+ try {
219
+ // Hash the tool (same process as signing) - EXACT webapp logic
220
+ const toolHash = await hashTool(toolObject);
221
+ // Convert Base64 signature to bytes EXACTLY like webapp
222
+ const signatureBytes = new Uint8Array(atob(signatureB64)
223
+ .split("")
224
+ .map((char) => char.charCodeAt(0)));
225
+ console.error("🔍 Tool hash byte length:", toolHash.length, "(should be 32 for SHA-256)");
226
+ console.error("🔍 Signature bytes length:", signatureBytes.length, "(should be 64 for P-256)");
227
+ // Use Web Crypto API for verification (matches webapp exactly)
228
+ const { webcrypto } = await import("node:crypto");
229
+ const isValid = await webcrypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, publicKeyObj, signatureBytes, toolHash);
230
+ console.error("🎯 Web Crypto API verification result:", isValid);
231
+ return isValid;
232
+ }
233
+ catch (error) {
234
+ console.error("❌ Verification error:", error);
235
+ return false;
236
+ }
237
+ }
238
+ /**
239
+ * Verify an Enact tool with embedded signatures against trusted keys
240
+ * Uses the exact same canonical format and verification approach as the webapp
241
+ */
242
+ export async function verifyTool(toolYaml, policy = DEFAULT_POLICY) {
243
+ const errors = [];
244
+ const verifiedSigners = [];
245
+ try {
246
+ // Get trusted public keys
247
+ const trustedKeys = getTrustedPublicKeysMap();
248
+ if (trustedKeys.size === 0) {
249
+ return {
250
+ isValid: false,
251
+ message: "No trusted public keys available",
252
+ validSignatures: 0,
253
+ totalSignatures: 0,
254
+ verifiedSigners: [],
255
+ errors: ["No trusted keys configured"],
256
+ };
257
+ }
258
+ if (process.env.DEBUG) {
259
+ console.error("Trusted keys available:");
260
+ for (const [key, pem] of trustedKeys.entries()) {
261
+ console.error(` Key: ${key.substring(0, 20)}...`);
262
+ }
263
+ }
264
+ // Parse the tool if it's YAML string
265
+ const tool = typeof toolYaml === "string" ? parse(toolYaml) : toolYaml;
266
+ // Check if tool has signatures
267
+ if (!tool.signatures || Object.keys(tool.signatures).length === 0) {
268
+ return {
269
+ isValid: false,
270
+ message: "No signatures found in the tool",
271
+ validSignatures: 0,
272
+ totalSignatures: 0,
273
+ verifiedSigners: [],
274
+ errors: ["No signatures found"],
275
+ };
276
+ }
277
+ const totalSignatures = Object.keys(tool.signatures).length;
278
+ // Create canonical JSON for verification (without signatures)
279
+ const toolForVerification = { ...tool };
280
+ delete toolForVerification.signatures;
281
+ // Use EXACT same canonical JSON creation as webapp
282
+ const toolHashBytes = await hashTool(toolForVerification);
283
+ // Debug output for verification
284
+ if (process.env.NODE_ENV === "development" || process.env.DEBUG) {
285
+ console.error("=== VERIFICATION DEBUG (WEBAPP COMPATIBLE) ===");
286
+ console.error("Original tool signature field:", Object.keys(tool.signatures || {}));
287
+ console.error("Tool before removing signatures:", JSON.stringify(tool, null, 2));
288
+ console.error("Tool for verification:", JSON.stringify(toolForVerification, null, 2));
289
+ console.error("Tool hash bytes length:", toolHashBytes.length, "(should be 32 for SHA-256)");
290
+ console.error("==============================================");
291
+ }
292
+ // Verify each signature
293
+ let validSignatures = 0;
294
+ for (const [publicKeyBase64, signatureData] of Object.entries(tool.signatures)) {
295
+ try {
296
+ // Check if algorithm is allowed
297
+ if (policy.allowedAlgorithms &&
298
+ !policy.allowedAlgorithms.includes(signatureData.algorithm)) {
299
+ errors.push(`Signature by ${signatureData.signer}: unsupported algorithm ${signatureData.algorithm}`);
300
+ continue;
301
+ }
302
+ // Check if signer is trusted (if policy specifies trusted signers)
303
+ if (policy.trustedSigners &&
304
+ !policy.trustedSigners.includes(signatureData.signer)) {
305
+ errors.push(`Signature by ${signatureData.signer}: signer not in trusted list`);
306
+ continue;
307
+ }
308
+ // Check if we have this public key in our trusted keys
309
+ const publicKeyPem = trustedKeys.get(publicKeyBase64);
310
+ if (!publicKeyPem) {
311
+ // Try to reconstruct PEM from base64 if not found directly
312
+ const reconstructedPem = base64ToPem(publicKeyBase64);
313
+ if (!trustedKeys.has(pemToBase64(reconstructedPem))) {
314
+ errors.push(`Signature by ${signatureData.signer}: public key not trusted`);
315
+ continue;
316
+ }
317
+ }
318
+ if (process.env.DEBUG) {
319
+ console.error("Looking for public key:", publicKeyBase64);
320
+ console.error("Key found in trusted keys:", !!publicKeyPem);
321
+ }
322
+ // Verify the signature using Web Crypto API (webapp compatible)
323
+ let isValid = false;
324
+ try {
325
+ const publicKeyToUse = publicKeyPem || base64ToPem(publicKeyBase64);
326
+ if (process.env.DEBUG) {
327
+ console.error("Signature base64:", signatureData.value);
328
+ console.error("Signature buffer length (should be 64):", Buffer.from(signatureData.value, "base64").length);
329
+ console.error("Public key base64:", publicKeyBase64);
330
+ }
331
+ if (signatureData.type === "ecdsa-p256") {
332
+ // Use Web Crypto API to match webapp exactly
333
+ const { webcrypto } = await import("node:crypto");
334
+ // Import the public key (convert PEM to raw key data like webapp)
335
+ const publicKeyData = crypto
336
+ .createPublicKey({
337
+ key: publicKeyToUse,
338
+ format: "pem",
339
+ type: "spki",
340
+ })
341
+ .export({ format: "der", type: "spki" });
342
+ const publicKeyObj = await webcrypto.subtle.importKey("spki", publicKeyData, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
343
+ // Use the centralized verification function (webapp compatible)
344
+ isValid = await verifyToolSignature(toolForVerification, signatureData.value, publicKeyObj);
345
+ if (process.env.DEBUG) {
346
+ console.error("Web Crypto API verification result (webapp compatible):", isValid);
347
+ }
348
+ }
349
+ else {
350
+ // Fallback for other signature types
351
+ const verify = crypto.createVerify("SHA256");
352
+ const canonicalJson = createCanonicalToolJson(toolForVerification);
353
+ verify.update(canonicalJson, "utf8");
354
+ const signature = Buffer.from(signatureData.value, "base64");
355
+ isValid = verify.verify(publicKeyToUse, signature);
356
+ }
357
+ }
358
+ catch (verifyError) {
359
+ errors.push(`Signature by ${signatureData.signer}: verification error - ${verifyError.message}`);
360
+ continue;
361
+ }
362
+ if (isValid) {
363
+ validSignatures++;
364
+ verifiedSigners.push({
365
+ signer: signatureData.signer,
366
+ role: signatureData.role,
367
+ keyId: publicKeyBase64.substring(0, 8), // First 8 chars as key ID
368
+ });
369
+ }
370
+ else {
371
+ errors.push(`Signature by ${signatureData.signer}: cryptographic verification failed`);
372
+ }
373
+ }
374
+ catch (error) {
375
+ errors.push(`Signature by ${signatureData.signer}: verification error - ${error.message}`);
376
+ }
377
+ }
378
+ // Apply policy checks
379
+ const policyErrors = [];
380
+ // Check minimum signatures
381
+ if (policy.minimumSignatures &&
382
+ validSignatures < policy.minimumSignatures) {
383
+ policyErrors.push(`Policy requires ${policy.minimumSignatures} signatures, but only ${validSignatures} valid`);
384
+ }
385
+ // Check required roles
386
+ if (policy.requireRoles && policy.requireRoles.length > 0) {
387
+ const verifiedRoles = verifiedSigners.map((s) => s.role).filter(Boolean);
388
+ const missingRoles = policy.requireRoles.filter((role) => !verifiedRoles.includes(role));
389
+ if (missingRoles.length > 0) {
390
+ policyErrors.push(`Policy requires roles: ${missingRoles.join(", ")}`);
391
+ }
392
+ }
393
+ const isValid = policyErrors.length === 0 && validSignatures > 0;
394
+ const allErrors = [...errors, ...policyErrors];
395
+ let message;
396
+ if (isValid) {
397
+ message = `Tool "${tool.name}" verified with ${validSignatures}/${totalSignatures} valid signatures`;
398
+ if (verifiedSigners.length > 0) {
399
+ const signerInfo = verifiedSigners
400
+ .map((s) => `${s.signer}${s.role ? ` (${s.role})` : ""}`)
401
+ .join(", ");
402
+ message += ` from: ${signerInfo}`;
403
+ }
404
+ }
405
+ else {
406
+ message = `Tool "${tool.name}" verification failed: ${allErrors[0] || "Unknown error"}`;
407
+ }
408
+ return {
409
+ isValid,
410
+ message,
411
+ validSignatures,
412
+ totalSignatures,
413
+ verifiedSigners,
414
+ errors: allErrors,
415
+ };
416
+ }
417
+ catch (error) {
418
+ return {
419
+ isValid: false,
420
+ message: `Verification error: ${error.message}`,
421
+ validSignatures: 0,
422
+ totalSignatures: 0,
423
+ verifiedSigners: [],
424
+ errors: [error.message],
425
+ };
426
+ }
427
+ }
428
+ /**
429
+ * Check if a tool should be executed based on verification policy
430
+ * @param tool Tool to check
431
+ * @param policy Verification policy
432
+ * @returns Whether execution should proceed
433
+ */
434
+ export async function shouldExecuteTool(tool, policy = DEFAULT_POLICY) {
435
+ const verification = await verifyTool(tool, policy);
436
+ if (verification.isValid) {
437
+ return {
438
+ allowed: true,
439
+ reason: `Verified: ${verification.message}`,
440
+ };
441
+ }
442
+ else {
443
+ return {
444
+ allowed: false,
445
+ reason: `Verification failed: ${verification.message}`,
446
+ };
447
+ }
448
+ }
449
+ /**
450
+ * Generate a new ECC key pair
451
+ */
452
+ export function generateKeyPair(outputDir, prefix = "enact") {
453
+ if (!fs.existsSync(outputDir)) {
454
+ fs.mkdirSync(outputDir, { recursive: true });
455
+ }
456
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ec", {
457
+ namedCurve: "prime256v1",
458
+ publicKeyEncoding: { type: "spki", format: "pem" },
459
+ privateKeyEncoding: { type: "pkcs8", format: "pem" },
460
+ });
461
+ const privateKeyPath = path.join(outputDir, `${prefix}-private.pem`);
462
+ const publicKeyPath = path.join(outputDir, `${prefix}-public.pem`);
463
+ fs.writeFileSync(privateKeyPath, privateKey);
464
+ fs.writeFileSync(publicKeyPath, publicKey);
465
+ return { privateKeyPath, publicKeyPath };
466
+ }
467
+ /**
468
+ * Add a public key to trusted keys
469
+ */
470
+ export function addTrustedKey(keyPath, keyName) {
471
+ if (!fs.existsSync(TRUSTED_KEYS_DIR)) {
472
+ fs.mkdirSync(TRUSTED_KEYS_DIR, { recursive: true });
473
+ }
474
+ const keyContent = fs.readFileSync(keyPath, "utf8");
475
+ const fileName = keyName || `trusted-key-${Date.now()}.pem`;
476
+ const trustedKeyPath = path.join(TRUSTED_KEYS_DIR, fileName);
477
+ fs.writeFileSync(trustedKeyPath, keyContent);
478
+ return trustedKeyPath;
479
+ }
480
+ /**
481
+ * List all trusted keys with their base64 representations
482
+ */
483
+ export function listTrustedKeys() {
484
+ const keyInfo = [];
485
+ if (fs.existsSync(TRUSTED_KEYS_DIR)) {
486
+ try {
487
+ const files = fs.readdirSync(TRUSTED_KEYS_DIR);
488
+ for (const file of files) {
489
+ if (file.endsWith(".pem")) {
490
+ const keyPath = path.join(TRUSTED_KEYS_DIR, file);
491
+ const keyContent = fs.readFileSync(keyPath, "utf8");
492
+ const base64Key = pemToBase64(keyContent);
493
+ const fingerprint = crypto
494
+ .createHash("sha256")
495
+ .update(keyContent)
496
+ .digest("hex")
497
+ .substring(0, 16);
498
+ keyInfo.push({
499
+ id: base64Key.substring(0, 8),
500
+ filename: file,
501
+ base64Key,
502
+ fingerprint,
503
+ });
504
+ }
505
+ }
506
+ }
507
+ catch (error) {
508
+ console.error(`Error reading trusted keys: ${error.message}`);
509
+ }
510
+ }
511
+ return keyInfo;
512
+ }
513
+ // Export verification policies for use in CLI/MCP server
514
+ export const VERIFICATION_POLICIES = {
515
+ // Permissive: any valid signature from trusted key
516
+ PERMISSIVE: {
517
+ minimumSignatures: 1,
518
+ allowedAlgorithms: ["sha256"],
519
+ },
520
+ // Strict: require author + reviewer signatures
521
+ ENTERPRISE: {
522
+ minimumSignatures: 2,
523
+ requireRoles: ["author", "reviewer"],
524
+ allowedAlgorithms: ["sha256"],
525
+ },
526
+ // Maximum security: require author + reviewer + approver
527
+ PARANOID: {
528
+ minimumSignatures: 3,
529
+ requireRoles: ["author", "reviewer", "approver"],
530
+ allowedAlgorithms: ["sha256"],
531
+ },
532
+ };
@@ -0,0 +1,41 @@
1
+ import type { EnactTool, ExecutionResult } from "../types";
2
+ export interface VerificationEnforcementOptions {
3
+ skipVerification?: boolean;
4
+ verifyPolicy?: "permissive" | "enterprise" | "paranoid";
5
+ force?: boolean;
6
+ allowUnsigned?: boolean;
7
+ }
8
+ export interface VerificationEnforcementResult {
9
+ allowed: boolean;
10
+ reason: string;
11
+ verificationResult?: {
12
+ isValid: boolean;
13
+ message: string;
14
+ validSignatures: number;
15
+ totalSignatures: number;
16
+ verifiedSigners: Array<{
17
+ signer: string;
18
+ role?: string;
19
+ keyId: string;
20
+ }>;
21
+ errors: string[];
22
+ };
23
+ error?: {
24
+ message: string;
25
+ code: string;
26
+ details?: any;
27
+ };
28
+ }
29
+ /**
30
+ * Enforce mandatory signature verification for tool execution
31
+ * This is the central function that should be called before ANY tool execution
32
+ */
33
+ export declare function enforceSignatureVerification(tool: EnactTool, options?: VerificationEnforcementOptions): Promise<VerificationEnforcementResult>;
34
+ /**
35
+ * Create an execution result for verification failure
36
+ */
37
+ export declare function createVerificationFailureResult(tool: EnactTool, verificationResult: VerificationEnforcementResult, executionId: string): ExecutionResult;
38
+ /**
39
+ * Log security audit information for tool execution
40
+ */
41
+ export declare function logSecurityAudit(tool: EnactTool, verificationResult: VerificationEnforcementResult, executionAllowed: boolean, options: VerificationEnforcementOptions): void;