@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
package/src/index.ts ADDED
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * @vellumai/credential-executor
4
+ *
5
+ * Credential Execution Service (CES) — an isolated runtime that executes
6
+ * credential-bearing tool operations on behalf of untrusted agents. The CES
7
+ * receives RPC requests from the assistant daemon, materialises credentials
8
+ * from the local credential store, executes the requested operation through
9
+ * the egress proxy, and returns sanitised results.
10
+ *
11
+ * This module re-exports the public API surface. For entrypoints see:
12
+ * - `main.ts` — local mode (stdio transport, child process)
13
+ * - `managed-main.ts` — managed mode (Unix socket transport, sidecar)
14
+ */
15
+
16
+ export {
17
+ CesRpcServer,
18
+ createCesServer,
19
+ createRunAuthenticatedCommandHandler,
20
+ registerCommandExecutionHandler,
21
+ createMakeAuthenticatedRequestHandler,
22
+ createManageSecureCommandToolHandler,
23
+ registerManageSecureCommandToolHandler,
24
+ buildHandlersWithHttp,
25
+ } from "./server.js";
26
+ export type {
27
+ CesServerOptions,
28
+ ManageSecureCommandToolHandlerDeps,
29
+ RpcHandlerRegistry,
30
+ RpcMethodHandler,
31
+ RunAuthenticatedCommandHandlerOptions,
32
+ } from "./server.js";
33
+
34
+ export {
35
+ getCesDataRoot,
36
+ getCesGrantsDir,
37
+ getCesAuditDir,
38
+ getCesToolStoreDir,
39
+ getCesMode,
40
+ getBootstrapSocketPath,
41
+ getHealthPort,
42
+ } from "./paths.js";
43
+ export type { CesMode } from "./paths.js";
44
+
45
+ export { PersistentGrantStore, TemporaryGrantStore } from "./grants/index.js";
46
+ export type {
47
+ PersistentGrant,
48
+ TemporaryGrant,
49
+ TemporaryGrantKind,
50
+ } from "./grants/index.js";
51
+
52
+ export { computeDigest, verifyDigest } from "./toolstore/integrity.js";
53
+ export type { DigestVerificationResult } from "./toolstore/integrity.js";
54
+
55
+ export {
56
+ isValidSha256Hex,
57
+ validateSourceUrl,
58
+ isWorkspaceOriginPath,
59
+ } from "./toolstore/manifest.js";
60
+ export type {
61
+ BundleOrigin,
62
+ ToolstoreManifest,
63
+ } from "./toolstore/manifest.js";
64
+
65
+ export {
66
+ publishBundle,
67
+ readPublishedManifest,
68
+ isBundlePublished,
69
+ getBundleDir,
70
+ getBundleManifestPath,
71
+ getBundleContentPath,
72
+ } from "./toolstore/publish.js";
73
+ export type {
74
+ PublishRequest,
75
+ PublishResult,
76
+ } from "./toolstore/publish.js";
77
+
78
+ export { resolveLocalSubject } from "./subjects/local.js";
79
+ export type {
80
+ ResolvedStaticSubject,
81
+ ResolvedOAuthSubject,
82
+ ResolvedLocalSubject,
83
+ SubjectResolutionResult,
84
+ OAuthConnectionLookup,
85
+ LocalSubjectResolverDeps,
86
+ } from "./subjects/local.js";
87
+
88
+ export { LocalMaterialiser } from "./materializers/local.js";
89
+ export type {
90
+ MaterialisedCredential,
91
+ MaterialisationResult,
92
+ TokenRefreshFn,
93
+ LocalMaterialiserDeps,
94
+ } from "./materializers/local.js";
95
+
96
+ export { executeAuthenticatedCommand } from "./commands/executor.js";
97
+ export type {
98
+ ExecuteCommandRequest,
99
+ ExecuteCommandResult,
100
+ CommandExecutorDeps,
101
+ MaterializeCredentialFn,
102
+ MaterializeCredentialResult,
103
+ } from "./commands/executor.js";
104
+
105
+ export { executeAuthenticatedHttpRequest } from "./http/executor.js";
106
+ export type { HttpExecutorDeps } from "./http/executor.js";
package/src/main.ts ADDED
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Local CES entrypoint.
4
+ *
5
+ * In local mode the assistant spawns CES as a child process and communicates
6
+ * over stdin/stdout using newline-delimited JSON. This entrypoint:
7
+ *
8
+ * 1. Ensures the CES-private data directories exist.
9
+ * 2. Starts the RPC server on process.stdin / process.stdout.
10
+ * 3. Shuts down cleanly when stdin closes (parent exit) or SIGTERM arrives.
11
+ *
12
+ * Local mode never opens a TCP listener or Unix socket. All communication
13
+ * flows through the inherited stdio file descriptors, which are automatically
14
+ * closed when the parent process exits.
15
+ *
16
+ * The stdio transport ensures that shell subprocesses spawned by CES
17
+ * (e.g. for `run_authenticated_command`) do not accidentally inherit the
18
+ * command channel — Bun's `Bun.spawn` defaults to "pipe" for stdio on
19
+ * child processes, so CES's own stdin/stdout are not leaked to subprocesses.
20
+ */
21
+
22
+ import { mkdirSync } from "node:fs";
23
+ import { homedir } from "node:os";
24
+ import { join } from "node:path";
25
+
26
+ import { CES_PROTOCOL_VERSION, CesRpcMethod } from "@vellumai/ces-contracts";
27
+ import { StaticCredentialMetadataStore } from "@vellumai/credential-storage";
28
+
29
+ import { AuditStore } from "./audit/store.js";
30
+ import { PersistentGrantStore } from "./grants/persistent-store.js";
31
+ import {
32
+ createListAuditRecordsHandler,
33
+ createListGrantsHandler,
34
+ createRecordGrantHandler,
35
+ createRevokeGrantHandler,
36
+ } from "./grants/rpc-handlers.js";
37
+ import { TemporaryGrantStore } from "./grants/temporary-store.js";
38
+ import { LocalMaterialiser } from "./materializers/local.js";
39
+ import {
40
+ getCesAuditDir,
41
+ getCesDataRoot,
42
+ getCesGrantsDir,
43
+ getCesToolStoreDir,
44
+ } from "./paths.js";
45
+ import {
46
+ buildHandlersWithHttp,
47
+ CesRpcServer,
48
+ registerCommandExecutionHandler,
49
+ registerManageSecureCommandToolHandler,
50
+ type RpcHandlerRegistry,
51
+ } from "./server.js";
52
+ import { publishBundle } from "./toolstore/publish.js";
53
+ import type { OAuthConnectionLookup } from "./subjects/local.js";
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Data directory bootstrap
57
+ // ---------------------------------------------------------------------------
58
+
59
+ function ensureDataDirs(): void {
60
+ const dirs = [
61
+ getCesDataRoot("local"),
62
+ getCesGrantsDir("local"),
63
+ getCesAuditDir("local"),
64
+ getCesToolStoreDir("local"),
65
+ ];
66
+ for (const dir of dirs) {
67
+ mkdirSync(dir, { recursive: true });
68
+ }
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // Vellum root resolution (mirrors assistant/src/util/platform.ts)
73
+ // ---------------------------------------------------------------------------
74
+
75
+ function getVellumRootDir(): string {
76
+ const baseDataDir = process.env["BASE_DATA_DIR"]?.trim();
77
+ return join(baseDataDir || homedir(), ".vellum");
78
+ }
79
+
80
+ // ---------------------------------------------------------------------------
81
+ // Build RPC handler registry
82
+ // ---------------------------------------------------------------------------
83
+
84
+ function buildHandlers(sessionId: string): RpcHandlerRegistry {
85
+ // -- Grant stores ----------------------------------------------------------
86
+ const persistentGrantStore = new PersistentGrantStore(
87
+ getCesGrantsDir("local"),
88
+ );
89
+ persistentGrantStore.init();
90
+
91
+ const temporaryGrantStore = new TemporaryGrantStore();
92
+
93
+ // -- Audit store -----------------------------------------------------------
94
+ const auditStore = new AuditStore(getCesAuditDir("local"));
95
+ auditStore.init();
96
+
97
+ // -- Credential backend (local) --------------------------------------------
98
+ // In local mode CES shares the filesystem with the assistant and can access
99
+ // the same credential metadata and secure-key stores.
100
+ const vellumRoot = getVellumRootDir();
101
+ const credentialMetadataPath = join(
102
+ vellumRoot,
103
+ "data",
104
+ "credential-metadata.json",
105
+ );
106
+ const metadataStore = new StaticCredentialMetadataStore(
107
+ credentialMetadataPath,
108
+ );
109
+
110
+ // Stub OAuth connection lookup — local OAuth connections are resolved from
111
+ // the assistant's SQLite database which CES cannot access directly. When
112
+ // local OAuth execution is needed, a connection bridge must be added.
113
+ const oauthConnections: OAuthConnectionLookup = {
114
+ getById: () => undefined,
115
+ };
116
+
117
+ // Stub SecureKeyBackend — in local mode the backend implementation lives in
118
+ // the assistant process (keychain or encrypted file store). CES cannot import
119
+ // it directly. HTTP/command handlers that require credential materialisation
120
+ // will return a structured error until a CES-native backend is wired in.
121
+ const stubSecureKeyBackend = {
122
+ async get(_key: string) {
123
+ return undefined;
124
+ },
125
+ async set(_key: string, _value: string) {
126
+ return false;
127
+ },
128
+ async delete(_key: string) {
129
+ return "error" as const;
130
+ },
131
+ async list() {
132
+ return [];
133
+ },
134
+ };
135
+
136
+ const localMaterialiser = new LocalMaterialiser({
137
+ secureKeyBackend: stubSecureKeyBackend,
138
+ });
139
+
140
+ // -- Build handler registry ------------------------------------------------
141
+
142
+ // Start with the HTTP handler (make_authenticated_request)
143
+ const handlers = buildHandlersWithHttp(
144
+ {
145
+ persistentGrantStore,
146
+ temporaryGrantStore,
147
+ localMaterialiser,
148
+ localSubjectDeps: {
149
+ metadataStore,
150
+ oauthConnections,
151
+ },
152
+ sessionId,
153
+ },
154
+ );
155
+
156
+ // Register run_authenticated_command handler
157
+ registerCommandExecutionHandler(handlers, {
158
+ executorDeps: {
159
+ persistentStore: persistentGrantStore,
160
+ temporaryStore: temporaryGrantStore,
161
+ materializeCredential: async (_handle) => ({
162
+ ok: false as const,
163
+ error:
164
+ "CES local credential materialisation not yet available. " +
165
+ "A CES-native secure-key backend must be configured.",
166
+ }),
167
+ cesMode: "local",
168
+ },
169
+ defaultWorkspaceDir: join(vellumRoot, "workspace"),
170
+ });
171
+
172
+ // Register manage_secure_command_tool handler
173
+ const toolRegistry = new Map<string, { toolName: string; credentialHandle: string; description: string; bundleDigest: string }>();
174
+
175
+ registerManageSecureCommandToolHandler(handlers, {
176
+ downloadBundle: async (sourceUrl: string) => {
177
+ const resp = await fetch(sourceUrl);
178
+ if (!resp.ok) {
179
+ throw new Error(`HTTP ${resp.status}: ${resp.statusText}`);
180
+ }
181
+ return Buffer.from(await resp.arrayBuffer());
182
+ },
183
+ publishBundle: (request) => publishBundle({ ...request, cesMode: "local" }),
184
+ unregisterTool: (toolName: string) => {
185
+ return toolRegistry.delete(toolName);
186
+ },
187
+ registerTool: (entry) => {
188
+ toolRegistry.set(entry.toolName, entry);
189
+ },
190
+ });
191
+
192
+ // Register grant management handlers
193
+ handlers[CesRpcMethod.RecordGrant] = createRecordGrantHandler({
194
+ persistentGrantStore,
195
+ temporaryGrantStore,
196
+ }) as typeof handlers[string];
197
+
198
+ handlers[CesRpcMethod.ListGrants] = createListGrantsHandler({
199
+ persistentGrantStore,
200
+ sessionId,
201
+ }) as typeof handlers[string];
202
+
203
+ handlers[CesRpcMethod.RevokeGrant] = createRevokeGrantHandler({
204
+ persistentGrantStore,
205
+ }) as typeof handlers[string];
206
+
207
+ // Register audit record handler
208
+ handlers[CesRpcMethod.ListAuditRecords] = createListAuditRecordsHandler({
209
+ auditStore,
210
+ }) as typeof handlers[string];
211
+
212
+ return handlers;
213
+ }
214
+
215
+ // ---------------------------------------------------------------------------
216
+ // Main
217
+ // ---------------------------------------------------------------------------
218
+
219
+ async function main(): Promise<void> {
220
+ ensureDataDirs();
221
+
222
+ const log = (msg: string) =>
223
+ process.stderr.write(`[ces-local] ${msg}\n`);
224
+
225
+ log(`Starting CES v${CES_PROTOCOL_VERSION} (local mode, stdio transport)`);
226
+
227
+ const controller = new AbortController();
228
+
229
+ // Graceful shutdown on SIGTERM / SIGINT
230
+ const shutdown = () => {
231
+ log("Shutting down...");
232
+ controller.abort();
233
+ };
234
+ process.on("SIGTERM", shutdown);
235
+ process.on("SIGINT", shutdown);
236
+
237
+ // Build the handler registry with all available RPC implementations
238
+ const sessionId = `ces-local-${Date.now()}`;
239
+ const handlers = buildHandlers(sessionId);
240
+
241
+ const server = new CesRpcServer({
242
+ input: process.stdin,
243
+ output: process.stdout,
244
+ handlers,
245
+ logger: {
246
+ log: (msg: string, ...args: unknown[]) =>
247
+ process.stderr.write(`[ces-local] ${msg} ${args.map(String).join(" ")}\n`),
248
+ warn: (msg: string, ...args: unknown[]) =>
249
+ process.stderr.write(`[ces-local] WARN: ${msg} ${args.map(String).join(" ")}\n`),
250
+ error: (msg: string, ...args: unknown[]) =>
251
+ process.stderr.write(`[ces-local] ERROR: ${msg} ${args.map(String).join(" ")}\n`),
252
+ },
253
+ signal: controller.signal,
254
+ });
255
+
256
+ await server.serve();
257
+ log("Server stopped.");
258
+ }
259
+
260
+ main().catch((err) => {
261
+ process.stderr.write(`[ces-local] Fatal: ${err}\n`);
262
+ process.exit(1);
263
+ });