@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.
- package/Dockerfile +55 -0
- package/bun.lock +37 -0
- package/package.json +32 -0
- package/src/__tests__/command-executor.test.ts +1333 -0
- package/src/__tests__/command-validator.test.ts +708 -0
- package/src/__tests__/command-workspace.test.ts +997 -0
- package/src/__tests__/grant-store.test.ts +467 -0
- package/src/__tests__/http-executor.test.ts +1251 -0
- package/src/__tests__/http-policy.test.ts +970 -0
- package/src/__tests__/local-materializers.test.ts +826 -0
- package/src/__tests__/managed-materializers.test.ts +961 -0
- package/src/__tests__/toolstore.test.ts +539 -0
- package/src/__tests__/transport.test.ts +388 -0
- package/src/audit/store.ts +188 -0
- package/src/commands/auth-adapters.ts +169 -0
- package/src/commands/executor.ts +840 -0
- package/src/commands/output-scan.ts +157 -0
- package/src/commands/profiles.ts +282 -0
- package/src/commands/validator.ts +438 -0
- package/src/commands/workspace.ts +512 -0
- package/src/grants/index.ts +17 -0
- package/src/grants/persistent-store.ts +247 -0
- package/src/grants/rpc-handlers.ts +269 -0
- package/src/grants/temporary-store.ts +219 -0
- package/src/http/audit.ts +84 -0
- package/src/http/executor.ts +540 -0
- package/src/http/path-template.ts +179 -0
- package/src/http/policy.ts +256 -0
- package/src/http/response-filter.ts +233 -0
- package/src/index.ts +106 -0
- package/src/main.ts +263 -0
- package/src/managed-main.ts +420 -0
- package/src/materializers/local.ts +300 -0
- package/src/materializers/managed-platform.ts +270 -0
- package/src/paths.ts +137 -0
- package/src/server.ts +636 -0
- package/src/subjects/local.ts +177 -0
- package/src/subjects/managed.ts +290 -0
- package/src/toolstore/integrity.ts +94 -0
- package/src/toolstore/manifest.ts +154 -0
- package/src/toolstore/publish.ts +342 -0
- 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
|
+
});
|