@vellumai/credential-executor 0.4.55

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 (42) hide show
  1. package/Dockerfile +55 -0
  2. package/bun.lock +37 -0
  3. package/package.json +32 -0
  4. package/src/__tests__/command-executor.test.ts +1333 -0
  5. package/src/__tests__/command-validator.test.ts +708 -0
  6. package/src/__tests__/command-workspace.test.ts +997 -0
  7. package/src/__tests__/grant-store.test.ts +467 -0
  8. package/src/__tests__/http-executor.test.ts +1251 -0
  9. package/src/__tests__/http-policy.test.ts +970 -0
  10. package/src/__tests__/local-materializers.test.ts +826 -0
  11. package/src/__tests__/managed-materializers.test.ts +961 -0
  12. package/src/__tests__/toolstore.test.ts +539 -0
  13. package/src/__tests__/transport.test.ts +388 -0
  14. package/src/audit/store.ts +188 -0
  15. package/src/commands/auth-adapters.ts +169 -0
  16. package/src/commands/executor.ts +840 -0
  17. package/src/commands/output-scan.ts +157 -0
  18. package/src/commands/profiles.ts +282 -0
  19. package/src/commands/validator.ts +438 -0
  20. package/src/commands/workspace.ts +512 -0
  21. package/src/grants/index.ts +17 -0
  22. package/src/grants/persistent-store.ts +247 -0
  23. package/src/grants/rpc-handlers.ts +269 -0
  24. package/src/grants/temporary-store.ts +219 -0
  25. package/src/http/audit.ts +84 -0
  26. package/src/http/executor.ts +540 -0
  27. package/src/http/path-template.ts +179 -0
  28. package/src/http/policy.ts +256 -0
  29. package/src/http/response-filter.ts +233 -0
  30. package/src/index.ts +106 -0
  31. package/src/main.ts +263 -0
  32. package/src/managed-main.ts +420 -0
  33. package/src/materializers/local.ts +300 -0
  34. package/src/materializers/managed-platform.ts +270 -0
  35. package/src/paths.ts +137 -0
  36. package/src/server.ts +636 -0
  37. package/src/subjects/local.ts +177 -0
  38. package/src/subjects/managed.ts +290 -0
  39. package/src/toolstore/integrity.ts +94 -0
  40. package/src/toolstore/manifest.ts +154 -0
  41. package/src/toolstore/publish.ts +342 -0
  42. package/tsconfig.json +20 -0
@@ -0,0 +1,539 @@
1
+ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
2
+ import { mkdirSync, rmSync, existsSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+
6
+ import {
7
+ type SecureCommandManifest,
8
+ MANIFEST_SCHEMA_VERSION,
9
+ EgressMode,
10
+ } from "../commands/profiles.js";
11
+ import { AuthAdapterType } from "../commands/auth-adapters.js";
12
+ import { computeDigest, verifyDigest } from "../toolstore/integrity.js";
13
+ import {
14
+ isValidSha256Hex,
15
+ validateSourceUrl,
16
+ isWorkspaceOriginPath,
17
+ } from "../toolstore/manifest.js";
18
+ import {
19
+ publishBundle,
20
+ readPublishedManifest,
21
+ isBundlePublished,
22
+ type PublishRequest,
23
+ } from "../toolstore/publish.js";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Test fixtures
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /** Sample bundle bytes (just some arbitrary content for testing). */
30
+ const SAMPLE_BUNDLE_BYTES = Buffer.from(
31
+ "#!/usr/bin/env bash\necho hello\n",
32
+ "utf-8",
33
+ );
34
+
35
+ /** The correct SHA-256 digest of SAMPLE_BUNDLE_BYTES. */
36
+ const SAMPLE_BUNDLE_DIGEST = computeDigest(SAMPLE_BUNDLE_BYTES);
37
+
38
+ /** A different set of bytes to test digest mismatches. */
39
+ const TAMPERED_BUNDLE_BYTES = Buffer.from(
40
+ "#!/usr/bin/env bash\nrm -rf /\n",
41
+ "utf-8",
42
+ );
43
+
44
+ /**
45
+ * Build a minimal valid SecureCommandManifest for testing.
46
+ */
47
+ function buildSecureManifest(
48
+ overrides: Partial<SecureCommandManifest> = {},
49
+ ): SecureCommandManifest {
50
+ return {
51
+ schemaVersion: MANIFEST_SCHEMA_VERSION,
52
+ bundleDigest: SAMPLE_BUNDLE_DIGEST,
53
+ bundleId: "test-cli",
54
+ version: "1.0.0",
55
+ entrypoint: "bin/test-cli",
56
+ commandProfiles: {
57
+ "read-data": {
58
+ description: "Read-only data access",
59
+ allowedArgvPatterns: [
60
+ { name: "list", tokens: ["list", "<resource>"] },
61
+ ],
62
+ deniedSubcommands: ["admin"],
63
+ allowedNetworkTargets: [
64
+ { hostPattern: "api.example.com", protocols: ["https"] },
65
+ ],
66
+ },
67
+ },
68
+ authAdapter: {
69
+ type: AuthAdapterType.EnvVar,
70
+ envVarName: "TEST_TOKEN",
71
+ },
72
+ egressMode: EgressMode.ProxyRequired,
73
+ ...overrides,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Build a valid PublishRequest for testing.
79
+ */
80
+ function buildPublishRequest(
81
+ overrides: Partial<PublishRequest> = {},
82
+ ): PublishRequest {
83
+ return {
84
+ bundleBytes: SAMPLE_BUNDLE_BYTES,
85
+ expectedDigest: SAMPLE_BUNDLE_DIGEST,
86
+ bundleId: "test-cli",
87
+ version: "1.0.0",
88
+ sourceUrl: "https://releases.example.com/test-cli/v1.0.0/bundle.tar.gz",
89
+ secureCommandManifest: buildSecureManifest(),
90
+ ...overrides,
91
+ };
92
+ }
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // Temp directory management for publisher tests
96
+ // ---------------------------------------------------------------------------
97
+
98
+ let testTmpDir: string;
99
+
100
+ beforeEach(() => {
101
+ testTmpDir = join(
102
+ tmpdir(),
103
+ `ces-toolstore-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
104
+ );
105
+ mkdirSync(testTmpDir, { recursive: true });
106
+
107
+ // Point CES data root to the temp directory so tests are isolated
108
+ process.env["BASE_DATA_DIR"] = testTmpDir;
109
+ });
110
+
111
+ afterEach(() => {
112
+ try {
113
+ rmSync(testTmpDir, { recursive: true, force: true });
114
+ } catch {
115
+ // Best effort cleanup
116
+ }
117
+ delete process.env["BASE_DATA_DIR"];
118
+ });
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Integrity: computeDigest / verifyDigest
122
+ // ---------------------------------------------------------------------------
123
+
124
+ describe("integrity", () => {
125
+ test("computeDigest returns a 64-character hex string", () => {
126
+ const digest = computeDigest(SAMPLE_BUNDLE_BYTES);
127
+ expect(digest).toHaveLength(64);
128
+ expect(/^[a-f0-9]{64}$/.test(digest)).toBe(true);
129
+ });
130
+
131
+ test("computeDigest is deterministic", () => {
132
+ const d1 = computeDigest(SAMPLE_BUNDLE_BYTES);
133
+ const d2 = computeDigest(SAMPLE_BUNDLE_BYTES);
134
+ expect(d1).toBe(d2);
135
+ });
136
+
137
+ test("computeDigest differs for different inputs", () => {
138
+ const d1 = computeDigest(SAMPLE_BUNDLE_BYTES);
139
+ const d2 = computeDigest(TAMPERED_BUNDLE_BYTES);
140
+ expect(d1).not.toBe(d2);
141
+ });
142
+
143
+ test("verifyDigest succeeds when digest matches", () => {
144
+ const result = verifyDigest(SAMPLE_BUNDLE_BYTES, SAMPLE_BUNDLE_DIGEST);
145
+ expect(result.valid).toBe(true);
146
+ expect(result.computedDigest).toBe(SAMPLE_BUNDLE_DIGEST);
147
+ expect(result.expectedDigest).toBe(SAMPLE_BUNDLE_DIGEST);
148
+ expect(result.error).toBeUndefined();
149
+ });
150
+
151
+ test("verifyDigest fails when digest does not match", () => {
152
+ const wrongDigest = computeDigest(TAMPERED_BUNDLE_BYTES);
153
+ const result = verifyDigest(SAMPLE_BUNDLE_BYTES, wrongDigest);
154
+ expect(result.valid).toBe(false);
155
+ expect(result.computedDigest).toBe(SAMPLE_BUNDLE_DIGEST);
156
+ expect(result.expectedDigest).toBe(wrongDigest);
157
+ expect(result.error).toContain("Digest mismatch");
158
+ });
159
+
160
+ test("verifyDigest fails for invalid hex digest", () => {
161
+ const result = verifyDigest(SAMPLE_BUNDLE_BYTES, "not-a-valid-hex-digest");
162
+ expect(result.valid).toBe(false);
163
+ expect(result.error).toContain("Digest mismatch");
164
+ });
165
+
166
+ test("verifyDigest fails for truncated digest", () => {
167
+ const truncated = SAMPLE_BUNDLE_DIGEST.slice(0, 32);
168
+ const result = verifyDigest(SAMPLE_BUNDLE_BYTES, truncated);
169
+ expect(result.valid).toBe(false);
170
+ expect(result.error).toContain("Digest mismatch");
171
+ });
172
+ });
173
+
174
+ // ---------------------------------------------------------------------------
175
+ // Manifest validation helpers
176
+ // ---------------------------------------------------------------------------
177
+
178
+ describe("manifest validation helpers", () => {
179
+ describe("isValidSha256Hex", () => {
180
+ test("accepts valid 64-char hex digest", () => {
181
+ expect(isValidSha256Hex(SAMPLE_BUNDLE_DIGEST)).toBe(true);
182
+ });
183
+
184
+ test("accepts all-zero digest", () => {
185
+ expect(isValidSha256Hex("0".repeat(64))).toBe(true);
186
+ });
187
+
188
+ test("rejects too-short digest", () => {
189
+ expect(isValidSha256Hex("abc123")).toBe(false);
190
+ });
191
+
192
+ test("rejects too-long digest", () => {
193
+ expect(isValidSha256Hex("a".repeat(65))).toBe(false);
194
+ });
195
+
196
+ test("rejects uppercase hex", () => {
197
+ expect(isValidSha256Hex("A".repeat(64))).toBe(false);
198
+ });
199
+
200
+ test("rejects non-hex characters", () => {
201
+ expect(isValidSha256Hex("g".repeat(64))).toBe(false);
202
+ });
203
+
204
+ test("rejects empty string", () => {
205
+ expect(isValidSha256Hex("")).toBe(false);
206
+ });
207
+ });
208
+
209
+ describe("validateSourceUrl", () => {
210
+ test("accepts valid HTTPS URL", () => {
211
+ expect(
212
+ validateSourceUrl("https://releases.example.com/v1/bundle.tar.gz"),
213
+ ).toBeNull();
214
+ });
215
+
216
+ test("rejects empty string", () => {
217
+ const err = validateSourceUrl("");
218
+ expect(err).not.toBeNull();
219
+ expect(err).toContain("required");
220
+ });
221
+
222
+ test("rejects file:// URL", () => {
223
+ const err = validateSourceUrl("file:///tmp/bundle.tar.gz");
224
+ expect(err).not.toBeNull();
225
+ expect(err).toContain("file:");
226
+ });
227
+
228
+ test("rejects data: URL", () => {
229
+ const err = validateSourceUrl("data:application/octet-stream;base64,AA==");
230
+ expect(err).not.toBeNull();
231
+ expect(err).toContain("data:");
232
+ });
233
+
234
+ test("rejects HTTP URL (not HTTPS)", () => {
235
+ const err = validateSourceUrl("http://example.com/bundle.tar.gz");
236
+ expect(err).not.toBeNull();
237
+ expect(err).toContain("HTTPS");
238
+ });
239
+
240
+ test("rejects non-URL string", () => {
241
+ const err = validateSourceUrl("/usr/local/bin/my-tool");
242
+ expect(err).not.toBeNull();
243
+ expect(err).toContain("not a valid URL");
244
+ });
245
+ });
246
+
247
+ describe("isWorkspaceOriginPath", () => {
248
+ test("detects .vellum/ paths", () => {
249
+ expect(isWorkspaceOriginPath("~/.vellum/workspace/tools/my-tool")).toBe(
250
+ true,
251
+ );
252
+ expect(isWorkspaceOriginPath(".vellum/workspace/tools/my-tool")).toBe(
253
+ true,
254
+ );
255
+ expect(
256
+ isWorkspaceOriginPath("/home/user/.vellum/workspace/tools/my-tool"),
257
+ ).toBe(true);
258
+ });
259
+
260
+ test("detects workspace paths", () => {
261
+ expect(isWorkspaceOriginPath("/some/path/workspace/tools/my-tool")).toBe(
262
+ true,
263
+ );
264
+ });
265
+
266
+ test("allows non-workspace paths", () => {
267
+ expect(isWorkspaceOriginPath("/usr/local/bin/gh")).toBe(false);
268
+ expect(isWorkspaceOriginPath("/opt/tools/my-cli")).toBe(false);
269
+ });
270
+ });
271
+ });
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // Publisher: digest mismatch rejection
275
+ // ---------------------------------------------------------------------------
276
+
277
+ describe("publishBundle — digest mismatch rejection", () => {
278
+ test("rejects bundle whose bytes do not match expectedDigest", () => {
279
+ const result = publishBundle(
280
+ buildPublishRequest({
281
+ bundleBytes: TAMPERED_BUNDLE_BYTES,
282
+ // expectedDigest is for SAMPLE_BUNDLE_BYTES, not TAMPERED_BUNDLE_BYTES
283
+ expectedDigest: SAMPLE_BUNDLE_DIGEST,
284
+ }),
285
+ );
286
+ expect(result.success).toBe(false);
287
+ expect(result.deduplicated).toBe(false);
288
+ expect(result.error).toContain("Digest mismatch");
289
+ });
290
+
291
+ test("rejects bundle with invalid digest format", () => {
292
+ const result = publishBundle(
293
+ buildPublishRequest({
294
+ expectedDigest: "not-a-valid-digest",
295
+ }),
296
+ );
297
+ expect(result.success).toBe(false);
298
+ expect(result.error).toContain("Invalid expectedDigest");
299
+ });
300
+
301
+ test("no files are written when digest mismatches", () => {
302
+ const wrongDigest = computeDigest(TAMPERED_BUNDLE_BYTES);
303
+ publishBundle(
304
+ buildPublishRequest({
305
+ bundleBytes: TAMPERED_BUNDLE_BYTES,
306
+ expectedDigest: SAMPLE_BUNDLE_DIGEST,
307
+ }),
308
+ );
309
+
310
+ // The toolstore directory should not contain a directory for the expected digest
311
+ const toolstoreDir = join(
312
+ testTmpDir,
313
+ ".vellum",
314
+ "protected",
315
+ "credential-executor",
316
+ "toolstore",
317
+ );
318
+ const bundleDir = join(toolstoreDir, SAMPLE_BUNDLE_DIGEST);
319
+ expect(existsSync(bundleDir)).toBe(false);
320
+
321
+ // Also check that no staging directories were left behind
322
+ if (existsSync(toolstoreDir)) {
323
+ const { readdirSync } = require("node:fs");
324
+ const entries = readdirSync(toolstoreDir) as string[];
325
+ const stagingDirs = entries.filter((e: string) => e.startsWith(".staging-"));
326
+ expect(stagingDirs).toHaveLength(0);
327
+ }
328
+ });
329
+ });
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // Publisher: immutable and deduplicated
333
+ // ---------------------------------------------------------------------------
334
+
335
+ describe("publishBundle — immutable and deduplicated by digest", () => {
336
+ test("first publish succeeds with deduplicated=false", () => {
337
+ const result = publishBundle(buildPublishRequest());
338
+ expect(result.success).toBe(true);
339
+ expect(result.deduplicated).toBe(false);
340
+ expect(result.bundlePath).toContain(SAMPLE_BUNDLE_DIGEST);
341
+ });
342
+
343
+ test("second publish of same digest returns deduplicated=true", () => {
344
+ const first = publishBundle(buildPublishRequest());
345
+ expect(first.success).toBe(true);
346
+ expect(first.deduplicated).toBe(false);
347
+
348
+ const second = publishBundle(buildPublishRequest());
349
+ expect(second.success).toBe(true);
350
+ expect(second.deduplicated).toBe(true);
351
+ expect(second.bundlePath).toBe(first.bundlePath);
352
+ });
353
+
354
+ test("published bundle content is readable and matches original bytes", () => {
355
+ const result = publishBundle(buildPublishRequest());
356
+ expect(result.success).toBe(true);
357
+
358
+ const bundleContentPath = join(result.bundlePath, "bundle.bin");
359
+ expect(existsSync(bundleContentPath)).toBe(true);
360
+
361
+ const readBack = readFileSync(bundleContentPath);
362
+ expect(Buffer.compare(readBack, SAMPLE_BUNDLE_BYTES)).toBe(0);
363
+ });
364
+
365
+ test("published manifest is readable and has correct fields", () => {
366
+ publishBundle(buildPublishRequest());
367
+
368
+ const manifest = readPublishedManifest(SAMPLE_BUNDLE_DIGEST);
369
+ expect(manifest).not.toBeNull();
370
+ expect(manifest!.digest).toBe(SAMPLE_BUNDLE_DIGEST);
371
+ expect(manifest!.bundleId).toBe("test-cli");
372
+ expect(manifest!.version).toBe("1.0.0");
373
+ expect(manifest!.origin.sourceUrl).toBe(
374
+ "https://releases.example.com/test-cli/v1.0.0/bundle.tar.gz",
375
+ );
376
+ expect(manifest!.declaredProfiles).toEqual(["read-data"]);
377
+ expect(manifest!.publishedAt).toBeTruthy();
378
+ });
379
+
380
+ test("isBundlePublished returns false before publish", () => {
381
+ expect(isBundlePublished(SAMPLE_BUNDLE_DIGEST)).toBe(false);
382
+ });
383
+
384
+ test("isBundlePublished returns true after publish", () => {
385
+ publishBundle(buildPublishRequest());
386
+ expect(isBundlePublished(SAMPLE_BUNDLE_DIGEST)).toBe(true);
387
+ });
388
+
389
+ test("different bundles with different digests are stored independently", () => {
390
+ // Publish first bundle
391
+ const firstResult = publishBundle(buildPublishRequest());
392
+ expect(firstResult.success).toBe(true);
393
+
394
+ // Publish a second, different bundle
395
+ const otherBytes = Buffer.from("#!/usr/bin/env bash\necho other\n", "utf-8");
396
+ const otherDigest = computeDigest(otherBytes);
397
+ const otherManifest = buildSecureManifest({
398
+ bundleDigest: otherDigest,
399
+ bundleId: "other-cli",
400
+ version: "2.0.0",
401
+ });
402
+
403
+ const secondResult = publishBundle(
404
+ buildPublishRequest({
405
+ bundleBytes: otherBytes,
406
+ expectedDigest: otherDigest,
407
+ bundleId: "other-cli",
408
+ version: "2.0.0",
409
+ sourceUrl: "https://releases.example.com/other-cli/v2.0.0/bundle.tar.gz",
410
+ secureCommandManifest: otherManifest,
411
+ }),
412
+ );
413
+ expect(secondResult.success).toBe(true);
414
+ expect(secondResult.deduplicated).toBe(false);
415
+ expect(secondResult.bundlePath).not.toBe(firstResult.bundlePath);
416
+
417
+ // Both are independently published
418
+ expect(isBundlePublished(SAMPLE_BUNDLE_DIGEST)).toBe(true);
419
+ expect(isBundlePublished(otherDigest)).toBe(true);
420
+ });
421
+ });
422
+
423
+ // ---------------------------------------------------------------------------
424
+ // Publisher: publication does not imply credential grant
425
+ // ---------------------------------------------------------------------------
426
+
427
+ describe("publishBundle — publication does not imply credential grant", () => {
428
+ test("publish result does not contain any grant or credential fields", () => {
429
+ const result = publishBundle(buildPublishRequest());
430
+ expect(result.success).toBe(true);
431
+
432
+ // The PublishResult type only has success, deduplicated, bundlePath, error.
433
+ // There are no grant-related fields.
434
+ const keys = Object.keys(result);
435
+ expect(keys).not.toContain("grant");
436
+ expect(keys).not.toContain("credential");
437
+ expect(keys).not.toContain("token");
438
+ expect(keys).not.toContain("secret");
439
+ });
440
+
441
+ test("toolstore manifest does not contain grant or credential data", () => {
442
+ publishBundle(buildPublishRequest());
443
+
444
+ const manifest = readPublishedManifest(SAMPLE_BUNDLE_DIGEST);
445
+ expect(manifest).not.toBeNull();
446
+
447
+ // Verify the manifest only contains content metadata, not grants
448
+ const keys = Object.keys(manifest!);
449
+ expect(keys).not.toContain("grant");
450
+ expect(keys).not.toContain("credential");
451
+ expect(keys).not.toContain("token");
452
+ expect(keys).not.toContain("secret");
453
+ expect(keys).toContain("digest");
454
+ expect(keys).toContain("bundleId");
455
+ expect(keys).toContain("origin");
456
+ expect(keys).toContain("declaredProfiles");
457
+ });
458
+ });
459
+
460
+ // ---------------------------------------------------------------------------
461
+ // Publisher: source URL validation
462
+ // ---------------------------------------------------------------------------
463
+
464
+ describe("publishBundle — source URL validation", () => {
465
+ test("rejects file:// source URL", () => {
466
+ const result = publishBundle(
467
+ buildPublishRequest({
468
+ sourceUrl: "file:///tmp/bundle.tar.gz",
469
+ }),
470
+ );
471
+ expect(result.success).toBe(false);
472
+ expect(result.error).toContain("file:");
473
+ });
474
+
475
+ test("rejects HTTP source URL", () => {
476
+ const result = publishBundle(
477
+ buildPublishRequest({
478
+ sourceUrl: "http://insecure.example.com/bundle.tar.gz",
479
+ }),
480
+ );
481
+ expect(result.success).toBe(false);
482
+ expect(result.error).toContain("HTTPS");
483
+ });
484
+
485
+ test("rejects workspace-origin source URL", () => {
486
+ const result = publishBundle(
487
+ buildPublishRequest({
488
+ sourceUrl: "https://example.com/.vellum/workspace/tools/bundle.tar.gz",
489
+ }),
490
+ );
491
+ expect(result.success).toBe(false);
492
+ expect(result.error).toContain("workspace-origin");
493
+ });
494
+
495
+ test("rejects empty source URL", () => {
496
+ const result = publishBundle(
497
+ buildPublishRequest({
498
+ sourceUrl: "",
499
+ }),
500
+ );
501
+ expect(result.success).toBe(false);
502
+ expect(result.error).toContain("required");
503
+ });
504
+ });
505
+
506
+ // ---------------------------------------------------------------------------
507
+ // Publisher: manifest validation pass-through
508
+ // ---------------------------------------------------------------------------
509
+
510
+ describe("publishBundle — manifest validation", () => {
511
+ test("rejects bundle with invalid secure command manifest", () => {
512
+ const invalidManifest = buildSecureManifest({
513
+ entrypoint: "/bin/bash", // denied binary
514
+ });
515
+
516
+ const result = publishBundle(
517
+ buildPublishRequest({
518
+ secureCommandManifest: invalidManifest,
519
+ }),
520
+ );
521
+ expect(result.success).toBe(false);
522
+ expect(result.error).toContain("Invalid secure command manifest");
523
+ expect(result.error).toContain("bash");
524
+ });
525
+
526
+ test("rejects bundle with empty command profiles", () => {
527
+ const invalidManifest = buildSecureManifest({
528
+ commandProfiles: {},
529
+ });
530
+
531
+ const result = publishBundle(
532
+ buildPublishRequest({
533
+ secureCommandManifest: invalidManifest,
534
+ }),
535
+ );
536
+ expect(result.success).toBe(false);
537
+ expect(result.error).toContain("Invalid secure command manifest");
538
+ });
539
+ });