@vellumai/credential-executor 0.10.7-dev.202607092332.9deedf4 → 0.10.7-dev.202607100313.fffdfea

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 (56) hide show
  1. package/node_modules/@vellumai/service-contracts/package.json +0 -2
  2. package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +0 -2
  3. package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +3 -5
  4. package/node_modules/@vellumai/service-contracts/src/index.ts +2 -4
  5. package/node_modules/@vellumai/service-contracts/src/rpc.ts +4 -447
  6. package/package.json +1 -1
  7. package/src/__tests__/managed-integration.test.ts +108 -87
  8. package/src/index.ts +5 -85
  9. package/src/main.ts +79 -549
  10. package/src/paths.ts +0 -15
  11. package/src/server.ts +52 -469
  12. package/node_modules/@vellumai/service-contracts/src/__tests__/grants.test.ts +0 -686
  13. package/node_modules/@vellumai/service-contracts/src/grants.ts +0 -184
  14. package/node_modules/@vellumai/service-contracts/src/rendering.ts +0 -135
  15. package/src/__tests__/command-executor.test.ts +0 -1879
  16. package/src/__tests__/command-validator.test.ts +0 -1405
  17. package/src/__tests__/command-workspace.test.ts +0 -1050
  18. package/src/__tests__/grant-store.test.ts +0 -689
  19. package/src/__tests__/http-executor.test.ts +0 -1336
  20. package/src/__tests__/http-policy.test.ts +0 -1069
  21. package/src/__tests__/local-materializers.test.ts +0 -860
  22. package/src/__tests__/local-token-refresh.test.ts +0 -361
  23. package/src/__tests__/manage-secure-command-tool.test.ts +0 -134
  24. package/src/__tests__/managed-lazy-getters.test.ts +0 -359
  25. package/src/__tests__/managed-materializers.test.ts +0 -1028
  26. package/src/__tests__/managed-rejection.test.ts +0 -43
  27. package/src/__tests__/toolstore.test.ts +0 -773
  28. package/src/audit/store.ts +0 -188
  29. package/src/commands/auth-adapters.ts +0 -169
  30. package/src/commands/egress-hooks.ts +0 -203
  31. package/src/commands/executor.ts +0 -1155
  32. package/src/commands/output-scan.ts +0 -157
  33. package/src/commands/profiles.ts +0 -286
  34. package/src/commands/validator.ts +0 -702
  35. package/src/commands/workspace.ts +0 -550
  36. package/src/grants/index.ts +0 -17
  37. package/src/grants/persistent-store.ts +0 -309
  38. package/src/grants/rpc-handlers.ts +0 -293
  39. package/src/grants/temporary-store.ts +0 -289
  40. package/src/http/audit.ts +0 -84
  41. package/src/http/executor.ts +0 -684
  42. package/src/http/path-template.ts +0 -245
  43. package/src/http/policy.ts +0 -238
  44. package/src/http/response-filter.ts +0 -233
  45. package/src/managed-errors.ts +0 -9
  46. package/src/managed-lazy-getters.ts +0 -106
  47. package/src/materializers/local-oauth-lookup.ts +0 -98
  48. package/src/materializers/local-token-refresh.ts +0 -287
  49. package/src/materializers/local.ts +0 -316
  50. package/src/materializers/managed-platform.ts +0 -295
  51. package/src/subjects/local.ts +0 -177
  52. package/src/subjects/managed.ts +0 -311
  53. package/src/subjects/policy.ts +0 -79
  54. package/src/toolstore/integrity.ts +0 -94
  55. package/src/toolstore/manifest.ts +0 -154
  56. package/src/toolstore/publish.ts +0 -571
@@ -1,233 +0,0 @@
1
- /**
2
- * HTTP response filtering for the Credential Execution Service.
3
- *
4
- * Sanitises raw HTTP responses before returning them to the untrusted
5
- * assistant runtime. The assistant must never receive:
6
- * - Raw auth-bearing response headers (e.g. `set-cookie`, `www-authenticate`)
7
- * - Echoed secret values in response bodies (defense-in-depth scrubbing)
8
- * - Unbounded response bodies that could exhaust memory
9
- *
10
- * The filter also produces a token-free audit summary of every HTTP
11
- * interaction for the CES audit log.
12
- */
13
-
14
- // ---------------------------------------------------------------------------
15
- // Configuration
16
- // ---------------------------------------------------------------------------
17
-
18
- /**
19
- * Maximum response body size returned to the assistant (256 KB).
20
- *
21
- * Responses larger than this are truncated with a suffix indicating
22
- * the original size. The full body is never stored — this is a hard
23
- * clamp, not a soft limit.
24
- */
25
- const MAX_BODY_BYTES = 256 * 1024;
26
-
27
- /**
28
- * Response headers that are safe to pass through to the assistant.
29
- *
30
- * Only these headers are included in the sanitised response.
31
- * Everything else is stripped — especially `set-cookie`,
32
- * `www-authenticate`, and any custom auth headers.
33
- */
34
- const ALLOWED_RESPONSE_HEADERS = new Set([
35
- "content-type",
36
- "content-length",
37
- "content-encoding",
38
- "content-language",
39
- "content-disposition",
40
- "cache-control",
41
- "etag",
42
- "last-modified",
43
- "date",
44
- "x-request-id",
45
- "x-ratelimit-limit",
46
- "x-ratelimit-remaining",
47
- "x-ratelimit-reset",
48
- "retry-after",
49
- "link",
50
- "location",
51
- "vary",
52
- "accept-ranges",
53
- "access-control-allow-origin",
54
- "access-control-allow-methods",
55
- "access-control-allow-headers",
56
- "access-control-expose-headers",
57
- ]);
58
-
59
- // ---------------------------------------------------------------------------
60
- // Types
61
- // ---------------------------------------------------------------------------
62
-
63
- /** Raw HTTP response from the outbound call. */
64
- export interface RawHttpResponse {
65
- /** HTTP status code. */
66
- statusCode: number;
67
- /** Raw response headers (key-value pairs, header names may be mixed case). */
68
- headers: Record<string, string>;
69
- /** Response body as a string. */
70
- body: string;
71
- }
72
-
73
- /** Sanitised HTTP response safe for the assistant runtime. */
74
- export interface SanitisedHttpResponse {
75
- /** HTTP status code (passed through). */
76
- statusCode: number;
77
- /** Whitelisted response headers (lowercased keys). */
78
- headers: Record<string, string>;
79
- /** Body clamped to MAX_BODY_BYTES with secrets scrubbed. */
80
- body: string;
81
- /** Whether the body was truncated. */
82
- truncated: boolean;
83
- /** Original body size in bytes (before truncation). */
84
- originalBodyBytes: number;
85
- }
86
-
87
- // ---------------------------------------------------------------------------
88
- // Header filtering
89
- // ---------------------------------------------------------------------------
90
-
91
- /**
92
- * Filter response headers to only include whitelisted safe headers.
93
- *
94
- * All header names are lowercased for consistent comparison.
95
- */
96
- export function filterResponseHeaders(
97
- headers: Record<string, string>,
98
- ): Record<string, string> {
99
- const filtered: Record<string, string> = {};
100
- for (const [key, value] of Object.entries(headers)) {
101
- if (ALLOWED_RESPONSE_HEADERS.has(key.toLowerCase())) {
102
- filtered[key.toLowerCase()] = value;
103
- }
104
- }
105
- return filtered;
106
- }
107
-
108
- // ---------------------------------------------------------------------------
109
- // Body clamping
110
- // ---------------------------------------------------------------------------
111
-
112
- /**
113
- * Clamp the response body to the maximum allowed size.
114
- *
115
- * Returns the (possibly truncated) body and metadata about truncation.
116
- */
117
- export function clampBody(body: string): {
118
- clampedBody: string;
119
- truncated: boolean;
120
- originalBytes: number;
121
- } {
122
- const bodyBytes = Buffer.byteLength(body, "utf-8");
123
-
124
- if (bodyBytes <= MAX_BODY_BYTES) {
125
- return {
126
- clampedBody: body,
127
- truncated: false,
128
- originalBytes: bodyBytes,
129
- };
130
- }
131
-
132
- // Truncate to MAX_BODY_BYTES. We use Buffer to handle multi-byte characters
133
- // correctly — slice at byte boundaries and convert back to string.
134
- const buf = Buffer.from(body, "utf-8");
135
- const truncatedBuf = buf.subarray(0, MAX_BODY_BYTES);
136
-
137
- // Decode back to string; incomplete multi-byte sequences at the end are
138
- // replaced with the Unicode replacement character, which is acceptable
139
- // for a truncated preview.
140
- const truncatedBody = truncatedBuf.toString("utf-8");
141
-
142
- return {
143
- clampedBody:
144
- truncatedBody +
145
- `\n\n[CES: Response truncated from ${bodyBytes} bytes to ${MAX_BODY_BYTES} bytes]`,
146
- truncated: true,
147
- originalBytes: bodyBytes,
148
- };
149
- }
150
-
151
- // ---------------------------------------------------------------------------
152
- // Secret scrubbing (defense-in-depth)
153
- // ---------------------------------------------------------------------------
154
-
155
- /**
156
- * Scrub exact occurrences of known secret values from a response body.
157
- *
158
- * This is a defense-in-depth measure for APIs that echo back auth tokens
159
- * or API keys in their response bodies. The scrubbing replaces exact
160
- * matches of the secret with a redacted placeholder.
161
- *
162
- * Limitations:
163
- * - Only scrubs exact matches (no partial or encoded variants).
164
- * - Short secrets (< 8 characters) are skipped to avoid false positives.
165
- * - This is NOT a primary security control — the grant system and
166
- * credential isolation are the real boundaries.
167
- */
168
- export function scrubSecrets(body: string, secrets: string[]): string {
169
- let result = body;
170
- for (const secret of secrets) {
171
- // Skip short secrets to avoid false positives with common substrings
172
- if (secret.length < 8) continue;
173
- // Use a simple global replace — secrets are treated as literal strings
174
- result = replaceAll(result, secret, "[CES:REDACTED]");
175
- }
176
- return result;
177
- }
178
-
179
- /**
180
- * Replace all occurrences of `search` in `str` with `replacement`.
181
- *
182
- * Uses a simple loop to avoid regex special-character escaping issues
183
- * with secret values that may contain regex metacharacters.
184
- */
185
- function replaceAll(str: string, search: string, replacement: string): string {
186
- if (search.length === 0) return str;
187
-
188
- let result = "";
189
- let idx = 0;
190
- while (idx < str.length) {
191
- const foundAt = str.indexOf(search, idx);
192
- if (foundAt === -1) {
193
- result += str.slice(idx);
194
- break;
195
- }
196
- result += str.slice(idx, foundAt) + replacement;
197
- idx = foundAt + search.length;
198
- }
199
- return result;
200
- }
201
-
202
- // ---------------------------------------------------------------------------
203
- // Full response filter
204
- // ---------------------------------------------------------------------------
205
-
206
- /**
207
- * Apply the full sanitisation pipeline to a raw HTTP response.
208
- *
209
- * Pipeline:
210
- * 1. Filter response headers to the whitelist.
211
- * 2. Clamp the body to MAX_BODY_BYTES.
212
- * 3. Scrub known secrets from the (already clamped) body.
213
- *
214
- * @param raw - The raw HTTP response from the outbound call.
215
- * @param secrets - Known secret values to scrub from the body.
216
- * @returns Sanitised response safe for the assistant runtime.
217
- */
218
- export function filterHttpResponse(
219
- raw: RawHttpResponse,
220
- secrets: string[] = [],
221
- ): SanitisedHttpResponse {
222
- const filteredHeaders = filterResponseHeaders(raw.headers);
223
- const { clampedBody, truncated, originalBytes } = clampBody(raw.body);
224
- const scrubbedBody = scrubSecrets(clampedBody, secrets);
225
-
226
- return {
227
- statusCode: raw.statusCode,
228
- headers: filteredHeaders,
229
- body: scrubbedBody,
230
- truncated,
231
- originalBodyBytes: originalBytes,
232
- };
233
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Error message constants for managed-mode CES.
3
- *
4
- * Re-exported from @vellumai/service-contracts so both the assistant and
5
- * credential-executor can share the constant via the approved shared-code
6
- * path without violating the hard process-boundary isolation.
7
- */
8
-
9
- export { MANAGED_LOCAL_STATIC_REJECTION_ERROR } from "@vellumai/service-contracts/credential-rpc";
@@ -1,106 +0,0 @@
1
- /**
2
- * Lazy getter pattern for managed CES API key propagation.
3
- *
4
- * In managed mode the assistant API key may arrive after CES handlers are
5
- * registered (via the handshake callback or a later RPC update). The
6
- * `ApiKeyRef` + `buildLazyGetters` pattern allows handlers to resolve
7
- * the key at call time rather than at registration time.
8
- *
9
- * Extracted from the CES entrypoint so the behavioral contract can be
10
- * tested directly without exercising the full managed bootstrap lifecycle.
11
- */
12
-
13
- import type { ManagedSubjectResolverOptions } from "./subjects/managed.js";
14
- import type { ManagedMaterializerOptions } from "./materializers/managed-platform.js";
15
-
16
- /**
17
- * Mutable reference to the assistant API key. Allows the handshake callback
18
- * to inject the key provisioned at runtime (which arrives after handlers are
19
- * built). Handlers read `.current` at call time, not at registration time.
20
- */
21
- export interface ApiKeyRef {
22
- current: string;
23
- }
24
-
25
- /**
26
- * Mutable reference to the platform assistant ID. The assistant ID is not
27
- * available at CES startup (warm-pool pods); the assistant forwards it via
28
- * the handshake or update_managed_credential RPC after provisioning, and
29
- * `.current` is updated so lazy getters pick it up.
30
- */
31
- export interface AssistantIdRef {
32
- current: string;
33
- }
34
-
35
- /**
36
- * Overwrite the session-scoped managed credential refs.
37
- *
38
- * The managed handler registry is long-lived — it persists across assistant
39
- * reconnects — so every handshake and every `update_managed_credential` must
40
- * fully overwrite these refs, including clearing them when a value is absent.
41
- * Otherwise a new or reprovisioned session could keep materializing platform
42
- * credentials with the previous session's API key or assistant ID. Absent
43
- * values fall back to "" (fail closed): the lazy getters then return no
44
- * materialization options, and `getAssistantApiKey` falls back to the env key.
45
- */
46
- export function applyManagedCredentialRefs(
47
- apiKeyRef: ApiKeyRef,
48
- assistantIdRef: AssistantIdRef,
49
- apiKey: string | undefined,
50
- assistantId: string | undefined,
51
- ): void {
52
- apiKeyRef.current = apiKey ?? "";
53
- assistantIdRef.current = assistantId ?? "";
54
- }
55
-
56
- export interface LazyGetterOptions {
57
- platformBaseUrl: string;
58
- assistantIdRef: AssistantIdRef;
59
- apiKeyRef: ApiKeyRef;
60
- envApiKey?: string;
61
- }
62
-
63
- export interface LazyGetters {
64
- getAssistantApiKey: () => string;
65
- getManagedSubjectOptions: () => ManagedSubjectResolverOptions | undefined;
66
- getManagedMaterializerOptions: () => ManagedMaterializerOptions | undefined;
67
- }
68
-
69
- /**
70
- * Build lazy getter functions that resolve the API key at call time.
71
- *
72
- * Prefers the handshake-provided key (via `apiKeyRef.current`) over the
73
- * env-var fallback, since in managed mode the env var may not be set
74
- * (chicken-and-egg: key is provisioned after hatch).
75
- */
76
- export function buildLazyGetters(opts: LazyGetterOptions): LazyGetters {
77
- const { platformBaseUrl, assistantIdRef, apiKeyRef, envApiKey } = opts;
78
-
79
- const getAssistantApiKey = (): string => apiKeyRef.current || envApiKey || "";
80
-
81
- const getManagedSubjectOptions = ():
82
- | ManagedSubjectResolverOptions
83
- | undefined => {
84
- const key = getAssistantApiKey();
85
- const id = assistantIdRef.current;
86
- return platformBaseUrl && key && id
87
- ? { platformBaseUrl, assistantApiKey: key, assistantId: id }
88
- : undefined;
89
- };
90
-
91
- const getManagedMaterializerOptions = ():
92
- | ManagedMaterializerOptions
93
- | undefined => {
94
- const key = getAssistantApiKey();
95
- const id = assistantIdRef.current;
96
- return platformBaseUrl && key && id
97
- ? { platformBaseUrl, assistantApiKey: key, assistantId: id }
98
- : undefined;
99
- };
100
-
101
- return {
102
- getAssistantApiKey,
103
- getManagedSubjectOptions,
104
- getManagedMaterializerOptions,
105
- };
106
- }
@@ -1,98 +0,0 @@
1
- /**
2
- * CES-native read-only OAuth connection lookup for local mode.
3
- *
4
- * In local mode, CES runs as a child process of the assistant on the same
5
- * machine and can read the assistant's SQLite database to look up OAuth
6
- * connections.
7
- *
8
- * This implementation opens the database in read-only mode and queries the
9
- * `oauth_connections` table directly using raw SQLite queries. It does not
10
- * use Drizzle ORM to avoid importing assistant-internal schema modules.
11
- *
12
- * The lookup is read-only — CES never modifies OAuth connection records.
13
- */
14
-
15
- import Database from "bun:sqlite";
16
- import { existsSync } from "node:fs";
17
- import { join } from "node:path";
18
-
19
- import type { OAuthConnectionRecord } from "@vellumai/credential-storage";
20
- import { oauthConnectionAccessTokenPath } from "@vellumai/credential-storage";
21
- import type { OAuthConnectionLookup } from "../subjects/local.js";
22
-
23
- // ---------------------------------------------------------------------------
24
- // Raw SQLite row shape (matches oauth_connections table)
25
- // ---------------------------------------------------------------------------
26
-
27
- interface OAuthConnectionRow {
28
- id: string;
29
- oauth_app_id: string;
30
- provider_key: string;
31
- account_info: string | null;
32
- granted_scopes: string;
33
- expires_at: number | null;
34
- has_refresh_token: number;
35
- status: string;
36
- label: string | null;
37
- metadata: string | null;
38
- created_at: number;
39
- updated_at: number;
40
- }
41
-
42
- // ---------------------------------------------------------------------------
43
- // Row → OAuthConnectionRecord mapping
44
- // ---------------------------------------------------------------------------
45
-
46
- function rowToRecord(row: OAuthConnectionRow): OAuthConnectionRecord {
47
- return {
48
- id: row.id,
49
- providerKey: row.provider_key,
50
- accountInfo: row.account_info,
51
- grantedScopes: JSON.parse(row.granted_scopes || "[]"),
52
- accessTokenPath: oauthConnectionAccessTokenPath(row.id),
53
- hasRefreshToken: row.has_refresh_token === 1,
54
- expiresAt: row.expires_at,
55
- createdAt: row.created_at,
56
- updatedAt: row.updated_at,
57
- };
58
- }
59
-
60
- // ---------------------------------------------------------------------------
61
- // Lookup implementation
62
- // ---------------------------------------------------------------------------
63
-
64
- /**
65
- * Create a read-only OAuth connection lookup backed by the assistant's
66
- * SQLite database.
67
- *
68
- * @param workspaceDir - The workspace directory (e.g. `$VELLUM_WORKSPACE_DIR`).
69
- */
70
- export function createLocalOAuthLookup(
71
- workspaceDir: string,
72
- ): OAuthConnectionLookup {
73
- const dbPath = join(workspaceDir, "data", "db", "assistant.db");
74
-
75
- return {
76
- getById(connectionId: string): OAuthConnectionRecord | undefined {
77
- if (!existsSync(dbPath)) return undefined;
78
-
79
- let db: Database | undefined;
80
- try {
81
- db = new Database(dbPath, { readonly: true });
82
- const row = db
83
- .query<
84
- OAuthConnectionRow,
85
- [string, string]
86
- >(`SELECT * FROM oauth_connections WHERE id = ? AND status = ? LIMIT 1`)
87
- .get(connectionId, "active");
88
-
89
- if (!row) return undefined;
90
- return rowToRecord(row);
91
- } catch {
92
- return undefined;
93
- } finally {
94
- db?.close();
95
- }
96
- },
97
- };
98
- }