@vellumai/credential-executor 0.4.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/Dockerfile +55 -0
  2. package/bun.lock +37 -0
  3. package/package.json +32 -0
  4. package/src/__tests__/command-executor.test.ts +1333 -0
  5. package/src/__tests__/command-validator.test.ts +708 -0
  6. package/src/__tests__/command-workspace.test.ts +997 -0
  7. package/src/__tests__/grant-store.test.ts +467 -0
  8. package/src/__tests__/http-executor.test.ts +1251 -0
  9. package/src/__tests__/http-policy.test.ts +970 -0
  10. package/src/__tests__/local-materializers.test.ts +826 -0
  11. package/src/__tests__/managed-materializers.test.ts +961 -0
  12. package/src/__tests__/toolstore.test.ts +539 -0
  13. package/src/__tests__/transport.test.ts +388 -0
  14. package/src/audit/store.ts +188 -0
  15. package/src/commands/auth-adapters.ts +169 -0
  16. package/src/commands/executor.ts +840 -0
  17. package/src/commands/output-scan.ts +157 -0
  18. package/src/commands/profiles.ts +282 -0
  19. package/src/commands/validator.ts +438 -0
  20. package/src/commands/workspace.ts +512 -0
  21. package/src/grants/index.ts +17 -0
  22. package/src/grants/persistent-store.ts +247 -0
  23. package/src/grants/rpc-handlers.ts +269 -0
  24. package/src/grants/temporary-store.ts +219 -0
  25. package/src/http/audit.ts +84 -0
  26. package/src/http/executor.ts +540 -0
  27. package/src/http/path-template.ts +179 -0
  28. package/src/http/policy.ts +256 -0
  29. package/src/http/response-filter.ts +233 -0
  30. package/src/index.ts +106 -0
  31. package/src/main.ts +263 -0
  32. package/src/managed-main.ts +420 -0
  33. package/src/materializers/local.ts +300 -0
  34. package/src/materializers/managed-platform.ts +270 -0
  35. package/src/paths.ts +137 -0
  36. package/src/server.ts +636 -0
  37. package/src/subjects/local.ts +177 -0
  38. package/src/subjects/managed.ts +290 -0
  39. package/src/toolstore/integrity.ts +94 -0
  40. package/src/toolstore/manifest.ts +154 -0
  41. package/src/toolstore/publish.ts +342 -0
  42. package/tsconfig.json +20 -0
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Toolstore manifest type definitions.
3
+ *
4
+ * Describes an approved secure command bundle that CES can publish into
5
+ * its private immutable toolstore. Each manifest records:
6
+ *
7
+ * - **sourceUrl** — The canonical URL the bundle was fetched from.
8
+ * - **expectedDigest** — SHA-256 hex digest that the downloaded bytes
9
+ * must match before publication.
10
+ * - **bundleId** — Unique identifier for the command bundle
11
+ * (e.g. "gh-cli", "aws-cli").
12
+ * - **version** — Semantic version of the bundle.
13
+ * - **commandProfiles** — Profile names from the secure command manifest
14
+ * that this bundle declares.
15
+ *
16
+ * ## Publishing rules
17
+ *
18
+ * 1. **Only CES can write** — bundles are published into the CES-private
19
+ * data root, which the assistant process cannot reach.
20
+ * 2. **Immutable once published** — a bundle directory keyed by its digest
21
+ * is never overwritten. Re-publishing the same digest is a no-op.
22
+ * 3. **Workspace-origin binaries are never publishable** — bundles must
23
+ * come from a known source URL; arbitrary assistant-provided bytes are
24
+ * rejected.
25
+ * 4. **Publication does not grant credentials** — writing a bundle into
26
+ * the toolstore is purely a content operation. Credential-use grants
27
+ * are managed by a separate subsystem.
28
+ */
29
+
30
+ import type { SecureCommandManifest } from "../commands/profiles.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Bundle origin
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * Describes the provenance of a bundle.
38
+ *
39
+ * `sourceUrl` must be an HTTPS URL. Workspace paths, file:// URLs, and
40
+ * data: URLs are structurally rejected.
41
+ */
42
+ export interface BundleOrigin {
43
+ /** HTTPS URL from which the bundle was fetched. */
44
+ sourceUrl: string;
45
+ /** ISO-8601 timestamp of when the bundle was fetched. */
46
+ fetchedAt: string;
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Toolstore manifest
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /**
54
+ * A toolstore manifest describes a single approved secure command bundle.
55
+ *
56
+ * This is the metadata stored alongside the bundle contents in the
57
+ * content-addressed toolstore directory.
58
+ */
59
+ export interface ToolstoreManifest {
60
+ /** SHA-256 hex digest of the bundle contents. Content-address key. */
61
+ digest: string;
62
+
63
+ /** Unique identifier for the command bundle. */
64
+ bundleId: string;
65
+
66
+ /** Semantic version of the bundle. */
67
+ version: string;
68
+
69
+ /** Provenance information — where the bundle came from. */
70
+ origin: BundleOrigin;
71
+
72
+ /** Profile names declared in the secure command manifest. */
73
+ declaredProfiles: string[];
74
+
75
+ /**
76
+ * The full secure command manifest embedded in the toolstore manifest.
77
+ * Used for runtime validation without needing to re-parse the bundle.
78
+ */
79
+ secureCommandManifest: SecureCommandManifest;
80
+
81
+ /** ISO-8601 timestamp of when the bundle was published to the toolstore. */
82
+ publishedAt: string;
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Validation helpers
87
+ // ---------------------------------------------------------------------------
88
+
89
+ /** Regex for a valid SHA-256 hex digest. */
90
+ const SHA256_HEX_PATTERN = /^[a-f0-9]{64}$/;
91
+
92
+ /**
93
+ * Returns true if the given string is a valid SHA-256 hex digest.
94
+ */
95
+ export function isValidSha256Hex(digest: string): boolean {
96
+ return SHA256_HEX_PATTERN.test(digest);
97
+ }
98
+
99
+ /**
100
+ * Schemes that are structurally rejected as bundle source URLs.
101
+ * Only HTTPS sources are accepted.
102
+ */
103
+ const REJECTED_URL_SCHEMES = ["file:", "data:", "blob:", "javascript:"];
104
+
105
+ /**
106
+ * Validate a source URL for bundle origin.
107
+ *
108
+ * Accepted: HTTPS URLs only.
109
+ * Rejected: file://, data://, workspace paths, non-URL strings.
110
+ *
111
+ * Returns an error message if invalid, or null if valid.
112
+ */
113
+ export function validateSourceUrl(sourceUrl: string): string | null {
114
+ if (!sourceUrl || sourceUrl.trim().length === 0) {
115
+ return "sourceUrl is required and must be non-empty.";
116
+ }
117
+
118
+ // Must be a valid URL
119
+ let parsed: URL;
120
+ try {
121
+ parsed = new URL(sourceUrl);
122
+ } catch {
123
+ return `sourceUrl "${sourceUrl}" is not a valid URL. Only HTTPS URLs are accepted.`;
124
+ }
125
+
126
+ // Must be HTTPS
127
+ if (parsed.protocol !== "https:") {
128
+ if (REJECTED_URL_SCHEMES.includes(parsed.protocol)) {
129
+ return `sourceUrl scheme "${parsed.protocol}" is not allowed. Only HTTPS URLs are accepted as bundle sources.`;
130
+ }
131
+ return `sourceUrl scheme "${parsed.protocol}" is not allowed. Only HTTPS URLs are accepted.`;
132
+ }
133
+
134
+ return null;
135
+ }
136
+
137
+ /**
138
+ * Workspace-origin path patterns that are never publishable as bundle
139
+ * sources. These catch attempts to publish assistant-provided bytes
140
+ * directly from the workspace directory.
141
+ */
142
+ const WORKSPACE_PATH_PATTERNS = [
143
+ /^~?\/?\.vellum\//,
144
+ /\/\.vellum\//,
145
+ /\/workspace\//i,
146
+ ] as const;
147
+
148
+ /**
149
+ * Returns true if the given path looks like a workspace-origin path
150
+ * that should never be accepted as a bundle source.
151
+ */
152
+ export function isWorkspaceOriginPath(path: string): boolean {
153
+ return WORKSPACE_PATH_PATTERNS.some((pattern) => pattern.test(path));
154
+ }
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Immutable toolstore publisher.
3
+ *
4
+ * Downloads (or re-fetches) secure command bundles inside CES, verifies
5
+ * their digest against the declared expected value, and writes them into
6
+ * content-addressed directories within the CES-private data root.
7
+ *
8
+ * ## Invariants
9
+ *
10
+ * 1. **CES-only writes** — All bundle content is written to the CES
11
+ * toolstore directory, which lives under the CES-private data root.
12
+ * The assistant process cannot read or write this path.
13
+ *
14
+ * 2. **Immutable publications** — Once a digest directory exists, it is
15
+ * never overwritten. Re-publishing the same digest is a deduplicated
16
+ * no-op that returns success.
17
+ *
18
+ * 3. **Digest verification before write** — Downloaded bytes are verified
19
+ * against the expected digest before any file is created. A mismatch
20
+ * is a hard error; no partial writes occur.
21
+ *
22
+ * 4. **No credential grants** — Publishing a bundle into the toolstore
23
+ * is a pure content operation. It does not create, modify, or imply
24
+ * any credential-use grant.
25
+ *
26
+ * 5. **No workspace-origin bundles** — Source URLs that point to the
27
+ * workspace directory or use non-HTTPS schemes are rejected.
28
+ */
29
+
30
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
31
+ import { join } from "node:path";
32
+
33
+ import { getCesToolStoreDir, type CesMode } from "../paths.js";
34
+ import type { SecureCommandManifest } from "../commands/profiles.js";
35
+ import { validateManifest } from "../commands/validator.js";
36
+ import { verifyDigest } from "./integrity.js";
37
+ import {
38
+ isValidSha256Hex,
39
+ isWorkspaceOriginPath,
40
+ validateSourceUrl,
41
+ type ToolstoreManifest,
42
+ } from "./manifest.js";
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Publication result
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export interface PublishResult {
49
+ /** Whether the publication succeeded. */
50
+ success: boolean;
51
+
52
+ /**
53
+ * Whether this was a deduplicated no-op (the digest directory already
54
+ * existed from a previous publication).
55
+ */
56
+ deduplicated: boolean;
57
+
58
+ /** The content-addressed directory path where the bundle is stored. */
59
+ bundlePath: string;
60
+
61
+ /** Error message if publication failed (undefined on success). */
62
+ error?: string;
63
+ }
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Publish request
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export interface PublishRequest {
70
+ /** Raw bundle bytes to publish. */
71
+ bundleBytes: Buffer | Uint8Array;
72
+
73
+ /** Expected SHA-256 hex digest of the bundle bytes. */
74
+ expectedDigest: string;
75
+
76
+ /** Unique identifier for the command bundle. */
77
+ bundleId: string;
78
+
79
+ /** Semantic version of the bundle. */
80
+ version: string;
81
+
82
+ /** HTTPS URL from which the bundle was fetched. */
83
+ sourceUrl: string;
84
+
85
+ /** The secure command manifest for this bundle. */
86
+ secureCommandManifest: SecureCommandManifest;
87
+
88
+ /** CES mode override (defaults to auto-detection). */
89
+ cesMode?: CesMode;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Content-addressed path helpers
94
+ // ---------------------------------------------------------------------------
95
+
96
+ /** Manifest filename within a content-addressed bundle directory. */
97
+ const MANIFEST_FILENAME = "toolstore-manifest.json";
98
+
99
+ /** Bundle content filename within a content-addressed bundle directory. */
100
+ const BUNDLE_FILENAME = "bundle.bin";
101
+
102
+ /**
103
+ * Return the content-addressed directory path for a given digest.
104
+ *
105
+ * Layout: `<toolstoreDir>/<digest>/`
106
+ */
107
+ export function getBundleDir(
108
+ toolstoreDir: string,
109
+ digest: string,
110
+ ): string {
111
+ return join(toolstoreDir, digest);
112
+ }
113
+
114
+ /**
115
+ * Return the manifest file path for a given digest.
116
+ */
117
+ export function getBundleManifestPath(
118
+ toolstoreDir: string,
119
+ digest: string,
120
+ ): string {
121
+ return join(getBundleDir(toolstoreDir, digest), MANIFEST_FILENAME);
122
+ }
123
+
124
+ /**
125
+ * Return the bundle content file path for a given digest.
126
+ */
127
+ export function getBundleContentPath(
128
+ toolstoreDir: string,
129
+ digest: string,
130
+ ): string {
131
+ return join(getBundleDir(toolstoreDir, digest), BUNDLE_FILENAME);
132
+ }
133
+
134
+ // ---------------------------------------------------------------------------
135
+ // Publisher
136
+ // ---------------------------------------------------------------------------
137
+
138
+ /**
139
+ * Publish a secure command bundle into the CES-private immutable
140
+ * toolstore.
141
+ *
142
+ * This function:
143
+ * 1. Validates the source URL (HTTPS only, no workspace origins).
144
+ * 2. Validates the expected digest format.
145
+ * 3. Validates the secure command manifest.
146
+ * 4. Verifies the bundle bytes match the expected digest.
147
+ * 5. Checks for deduplication (returns early if already published).
148
+ * 6. Writes bundle contents and manifest atomically.
149
+ *
150
+ * Returns a {@link PublishResult} describing the outcome.
151
+ */
152
+ export function publishBundle(request: PublishRequest): PublishResult {
153
+ const {
154
+ bundleBytes,
155
+ expectedDigest,
156
+ bundleId,
157
+ version,
158
+ sourceUrl,
159
+ secureCommandManifest,
160
+ cesMode,
161
+ } = request;
162
+
163
+ const toolstoreDir = getCesToolStoreDir(cesMode);
164
+
165
+ // -- Validate source URL ------------------------------------------------
166
+ const urlError = validateSourceUrl(sourceUrl);
167
+ if (urlError) {
168
+ return {
169
+ success: false,
170
+ deduplicated: false,
171
+ bundlePath: "",
172
+ error: `Invalid source URL: ${urlError}`,
173
+ };
174
+ }
175
+
176
+ // -- Reject workspace-origin paths in the URL ---------------------------
177
+ try {
178
+ const parsedUrl = new URL(sourceUrl);
179
+ if (isWorkspaceOriginPath(parsedUrl.pathname)) {
180
+ return {
181
+ success: false,
182
+ deduplicated: false,
183
+ bundlePath: "",
184
+ error: `Source URL path "${parsedUrl.pathname}" appears to be a workspace-origin path. ` +
185
+ `Workspace-origin binaries are never publishable.`,
186
+ };
187
+ }
188
+ } catch {
189
+ // URL parsing already validated above
190
+ }
191
+
192
+ // -- Validate digest format ---------------------------------------------
193
+ if (!isValidSha256Hex(expectedDigest)) {
194
+ return {
195
+ success: false,
196
+ deduplicated: false,
197
+ bundlePath: "",
198
+ error: `Invalid expectedDigest "${expectedDigest}". ` +
199
+ `Must be a 64-character lowercase hex SHA-256 digest.`,
200
+ };
201
+ }
202
+
203
+ // -- Validate secure command manifest -----------------------------------
204
+ const manifestValidation = validateManifest(secureCommandManifest);
205
+ if (!manifestValidation.valid) {
206
+ return {
207
+ success: false,
208
+ deduplicated: false,
209
+ bundlePath: "",
210
+ error: `Invalid secure command manifest: ${manifestValidation.errors.join("; ")}`,
211
+ };
212
+ }
213
+
214
+ // -- Verify bundle digest -----------------------------------------------
215
+ const digestResult = verifyDigest(bundleBytes, expectedDigest);
216
+ if (!digestResult.valid) {
217
+ return {
218
+ success: false,
219
+ deduplicated: false,
220
+ bundlePath: "",
221
+ error: digestResult.error!,
222
+ };
223
+ }
224
+
225
+ // -- Deduplication check ------------------------------------------------
226
+ const bundleDir = getBundleDir(toolstoreDir, expectedDigest);
227
+ const manifestPath = getBundleManifestPath(toolstoreDir, expectedDigest);
228
+
229
+ if (existsSync(bundleDir) && existsSync(manifestPath)) {
230
+ // Already published — deduplicated no-op
231
+ return {
232
+ success: true,
233
+ deduplicated: true,
234
+ bundlePath: bundleDir,
235
+ };
236
+ }
237
+
238
+ // -- Ensure toolstore directory exists -----------------------------------
239
+ mkdirSync(toolstoreDir, { recursive: true });
240
+
241
+ // -- Write bundle atomically --------------------------------------------
242
+ //
243
+ // Write to a staging directory first, then rename to the final
244
+ // content-addressed path. This prevents partial writes from being
245
+ // visible to readers.
246
+ const stagingDir = join(toolstoreDir, `.staging-${expectedDigest}-${Date.now()}`);
247
+ mkdirSync(stagingDir, { recursive: true });
248
+
249
+ try {
250
+ // Write bundle content
251
+ const stagingBundlePath = join(stagingDir, BUNDLE_FILENAME);
252
+ writeFileSync(stagingBundlePath, bundleBytes, { mode: 0o444 });
253
+
254
+ // Build and write toolstore manifest
255
+ const toolstoreManifest: ToolstoreManifest = {
256
+ digest: expectedDigest,
257
+ bundleId,
258
+ version,
259
+ origin: {
260
+ sourceUrl,
261
+ fetchedAt: new Date().toISOString(),
262
+ },
263
+ declaredProfiles: Object.keys(secureCommandManifest.commandProfiles),
264
+ secureCommandManifest,
265
+ publishedAt: new Date().toISOString(),
266
+ };
267
+
268
+ const stagingManifestPath = join(stagingDir, MANIFEST_FILENAME);
269
+ writeFileSync(
270
+ stagingManifestPath,
271
+ JSON.stringify(toolstoreManifest, null, 2) + "\n",
272
+ { mode: 0o444 },
273
+ );
274
+
275
+ // Rename staging directory to final content-addressed path
276
+ //
277
+ // On POSIX, rename() is atomic within a filesystem. Since the
278
+ // staging dir is in the same parent as the final dir, this is
279
+ // a same-filesystem rename.
280
+ renameSync(stagingDir, bundleDir);
281
+ } catch (err) {
282
+ // Clean up staging directory on failure
283
+ try {
284
+ rmSync(stagingDir, { recursive: true, force: true });
285
+ } catch {
286
+ // Best effort cleanup
287
+ }
288
+ return {
289
+ success: false,
290
+ deduplicated: false,
291
+ bundlePath: "",
292
+ error: `Failed to write bundle to toolstore: ${err instanceof Error ? err.message : String(err)}`,
293
+ };
294
+ }
295
+
296
+ return {
297
+ success: true,
298
+ deduplicated: false,
299
+ bundlePath: bundleDir,
300
+ };
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Reader helpers
305
+ // ---------------------------------------------------------------------------
306
+
307
+ /**
308
+ * Read a published toolstore manifest by digest.
309
+ *
310
+ * Returns null if no bundle with the given digest is published.
311
+ */
312
+ export function readPublishedManifest(
313
+ digest: string,
314
+ cesMode?: CesMode,
315
+ ): ToolstoreManifest | null {
316
+ const toolstoreDir = getCesToolStoreDir(cesMode);
317
+ const manifestPath = getBundleManifestPath(toolstoreDir, digest);
318
+
319
+ if (!existsSync(manifestPath)) {
320
+ return null;
321
+ }
322
+
323
+ try {
324
+ const raw = readFileSync(manifestPath, "utf-8");
325
+ return JSON.parse(raw) as ToolstoreManifest;
326
+ } catch {
327
+ return null;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Check if a bundle with the given digest is published in the toolstore.
333
+ */
334
+ export function isBundlePublished(
335
+ digest: string,
336
+ cesMode?: CesMode,
337
+ ): boolean {
338
+ const toolstoreDir = getCesToolStoreDir(cesMode);
339
+ const bundleDir = getBundleDir(toolstoreDir, digest);
340
+ const manifestPath = getBundleManifestPath(toolstoreDir, digest);
341
+ return existsSync(bundleDir) && existsSync(manifestPath);
342
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "resolveJsonModule": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "types": ["bun-types"]
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }