@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,295 +0,0 @@
1
- /**
2
- * Managed platform OAuth materializer.
3
- *
4
- * Materializes a `platform_oauth` handle into a short-lived access token
5
- * by calling the platform's CES token-materialization endpoint. The
6
- * materialized token is returned to the caller for immediate use (e.g.
7
- * injection into an HTTP request or command environment) but is **never**
8
- * persisted to any local storage — it exists only in memory for the
9
- * duration of the execution.
10
- *
11
- * Security invariants:
12
- * - Materialized tokens are never written to disk.
13
- * - Materialized tokens are never logged (not even partially).
14
- * - Platform errors are surfaced as structured errors without leaking secrets.
15
- * - If the platform cannot be reached, materialization fails closed.
16
- *
17
- * The materializer expects a resolved `ManagedSubject` from
18
- * `subjects/managed.ts`. It does not perform handle parsing or catalog
19
- * lookup — that is the resolver's responsibility.
20
- */
21
-
22
- import type { ManagedSubject } from "../subjects/managed.js";
23
-
24
- // ---------------------------------------------------------------------------
25
- // Materialization result
26
- // ---------------------------------------------------------------------------
27
-
28
- /**
29
- * Successful materialization result.
30
- *
31
- * The `accessToken` field contains the short-lived token obtained from
32
- * the platform. Callers MUST NOT persist this value — it should be used
33
- * immediately for request injection and then discarded.
34
- */
35
- export interface MaterializedToken {
36
- /** The short-lived access token. */
37
- accessToken: string;
38
- /** Token type (typically "Bearer"). */
39
- tokenType: string;
40
- /** Epoch ms when the token expires (null if the platform didn't report expiry). */
41
- expiresAt: number | null;
42
- /** Provider key (mirrored from the subject for convenience). */
43
- provider: string;
44
- /** Connection ID (mirrored from the subject for convenience). */
45
- connectionId: string;
46
- }
47
-
48
- export type MaterializeResult =
49
- | { ok: true; token: MaterializedToken }
50
- | { ok: false; error: MaterializationError };
51
-
52
- // ---------------------------------------------------------------------------
53
- // Materialization errors
54
- // ---------------------------------------------------------------------------
55
-
56
- export class MaterializationError extends Error {
57
- readonly code: string;
58
-
59
- constructor(code: string, message: string) {
60
- super(message);
61
- this.name = "MaterializationError";
62
- this.code = code;
63
- }
64
- }
65
-
66
- // ---------------------------------------------------------------------------
67
- // Platform token response shape
68
- // ---------------------------------------------------------------------------
69
-
70
- /**
71
- * Shape of the platform's CES token-materialization response.
72
- *
73
- * Field names match the platform's ManagedTokenMaterializeResponseSerializer:
74
- * access_token, token_type, expires_at, provider, handle
75
- *
76
- * The platform issues a short-lived access token for the specified
77
- * connection. The token is pre-authorized for the scopes granted on
78
- * the connection.
79
- */
80
- interface PlatformTokenResponse {
81
- access_token: string;
82
- token_type?: string;
83
- /** ISO-8601 datetime when the token expires (null if no expiry). */
84
- expires_at?: string | null;
85
- provider?: string;
86
- handle?: string;
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Materializer options
91
- // ---------------------------------------------------------------------------
92
-
93
- export interface ManagedMaterializerOptions {
94
- /**
95
- * Platform base URL (without trailing slash).
96
- */
97
- platformBaseUrl: string;
98
- /**
99
- * Assistant API key for authenticating with the platform.
100
- */
101
- assistantApiKey: string;
102
- /**
103
- * Platform-assigned assistant UUID. Required for building the
104
- * platform materialize URL: /v1/assistants/<id>/oauth/managed/materialize/
105
- */
106
- assistantId: string;
107
- /**
108
- * Optional custom fetch implementation (for testing).
109
- */
110
- fetch?: typeof globalThis.fetch;
111
- }
112
-
113
- // ---------------------------------------------------------------------------
114
- // Materializer implementation
115
- // ---------------------------------------------------------------------------
116
-
117
- /**
118
- * Materialize a managed OAuth subject into a short-lived access token
119
- * by calling the platform's token-materialization endpoint.
120
- *
121
- * The endpoint is:
122
- * POST {platformBaseUrl}/v1/assistants/{assistantId}/oauth/managed/materialize/
123
- *
124
- * The request body contains `{ connection_id: <uuid> }`.
125
- *
126
- * The platform validates the assistant API key, checks that the connection
127
- * is active, and returns a fresh access token (refreshing upstream if
128
- * needed).
129
- *
130
- * Fail-closed: any error results in a structured `MaterializationError`
131
- * rather than a partial or fallback result.
132
- */
133
- export async function materializeManagedToken(
134
- subject: ManagedSubject,
135
- options: ManagedMaterializerOptions
136
- ): Promise<MaterializeResult> {
137
- // -- Validate prerequisites -----------------------------------------------
138
- if (!options.platformBaseUrl) {
139
- return {
140
- ok: false,
141
- error: new MaterializationError(
142
- "MISSING_PLATFORM_URL",
143
- "Platform base URL is required for managed token materialization"
144
- ),
145
- };
146
- }
147
-
148
- if (!options.assistantApiKey) {
149
- return {
150
- ok: false,
151
- error: new MaterializationError(
152
- "MISSING_API_KEY",
153
- "Assistant API key is required for managed token materialization"
154
- ),
155
- };
156
- }
157
-
158
- if (!options.assistantId) {
159
- return {
160
- ok: false,
161
- error: new MaterializationError(
162
- "MISSING_ASSISTANT_ID",
163
- "Assistant ID is required for managed token materialization"
164
- ),
165
- };
166
- }
167
-
168
- // -- Call platform token endpoint -----------------------------------------
169
- const fetchFn = options.fetch ?? globalThis.fetch;
170
- const materializeUrl = `${
171
- options.platformBaseUrl
172
- }/v1/assistants/${encodeURIComponent(
173
- options.assistantId
174
- )}/oauth/managed/materialize/`;
175
-
176
- let response: Response;
177
- try {
178
- response = await fetchFn(materializeUrl, {
179
- method: "POST",
180
- headers: {
181
- Authorization: `Api-Key ${options.assistantApiKey}`,
182
- Accept: "application/json",
183
- "Content-Type": "application/json",
184
- },
185
- body: JSON.stringify({ connection_id: subject.connectionId }),
186
- });
187
- } catch (err) {
188
- const message = err instanceof Error ? err.message : String(err);
189
- return {
190
- ok: false,
191
- error: new MaterializationError(
192
- "PLATFORM_UNREACHABLE",
193
- `Failed to reach platform token endpoint: ${sanitizeError(message)}`
194
- ),
195
- };
196
- }
197
-
198
- // -- Handle error responses -----------------------------------------------
199
- if (!response.ok) {
200
- return {
201
- ok: false,
202
- error: mapPlatformError(response.status, subject.connectionId),
203
- };
204
- }
205
-
206
- // -- Parse token response -------------------------------------------------
207
- let body: PlatformTokenResponse;
208
- try {
209
- body = (await response.json()) as PlatformTokenResponse;
210
- } catch {
211
- return {
212
- ok: false,
213
- error: new MaterializationError(
214
- "INVALID_TOKEN_RESPONSE",
215
- "Platform token endpoint returned invalid JSON"
216
- ),
217
- };
218
- }
219
-
220
- if (!body.access_token || typeof body.access_token !== "string") {
221
- return {
222
- ok: false,
223
- error: new MaterializationError(
224
- "INVALID_TOKEN_RESPONSE",
225
- "Platform token response missing access_token"
226
- ),
227
- };
228
- }
229
-
230
- // -- Build materialized token ---------------------------------------------
231
- const expiresAt = parseExpiresAt(body.expires_at);
232
-
233
- const token: MaterializedToken = {
234
- accessToken: body.access_token,
235
- tokenType: body.token_type ?? "Bearer",
236
- expiresAt,
237
- provider: subject.provider,
238
- connectionId: subject.connectionId,
239
- };
240
-
241
- return { ok: true, token };
242
- }
243
-
244
- // ---------------------------------------------------------------------------
245
- // Helpers
246
- // ---------------------------------------------------------------------------
247
-
248
- /**
249
- * Map a platform HTTP error status to a structured MaterializationError.
250
- */
251
- function mapPlatformError(
252
- status: number,
253
- connectionId: string
254
- ): MaterializationError {
255
- switch (status) {
256
- case 401:
257
- return new MaterializationError(
258
- "PLATFORM_AUTH_FAILED",
259
- "Assistant API key is invalid or expired (HTTP 401)"
260
- );
261
- case 403:
262
- return new MaterializationError(
263
- "PLATFORM_FORBIDDEN",
264
- "Assistant is not authorized to materialize this connection (HTTP 403)"
265
- );
266
- case 404:
267
- return new MaterializationError(
268
- "CONNECTION_NOT_FOUND",
269
- `Connection ${connectionId} not found on the platform (HTTP 404)`
270
- );
271
- default:
272
- return new MaterializationError(
273
- `PLATFORM_HTTP_${status}`,
274
- `Platform token endpoint returned HTTP ${status}`
275
- );
276
- }
277
- }
278
-
279
- /**
280
- * Parse an ISO-8601 `expires_at` datetime string into epoch milliseconds.
281
- * Returns null if the value is missing or invalid.
282
- */
283
- function parseExpiresAt(expiresAt: string | null | undefined): number | null {
284
- if (expiresAt == null) return null;
285
- const ts = new Date(expiresAt).getTime();
286
- if (Number.isNaN(ts)) return null;
287
- return ts;
288
- }
289
-
290
- /**
291
- * Sanitize error messages to avoid leaking secrets.
292
- */
293
- function sanitizeError(message: string): string {
294
- return message.replace(/Api-Key\s+\S+/gi, "Api-Key [REDACTED]");
295
- }
@@ -1,177 +0,0 @@
1
- /**
2
- * CES local subject resolution.
3
- *
4
- * Resolves CES credential handles to their underlying storage subjects
5
- * using the shared `@vellumai/credential-storage` primitives. This module
6
- * is the CES-side counterpart to the assistant's credential resolver, but
7
- * operates independently — it never imports from the assistant daemon.
8
- *
9
- * Subject resolution is the first phase of credential materialisation:
10
- * 1. Parse the handle (via `@vellumai/service-contracts`)
11
- * 2. Look up the metadata/connection record in local storage
12
- * 3. Return a resolved subject that the materialiser can consume
13
- *
14
- * Both `local_static` and `local_oauth` handle types are supported.
15
- * Unknown or disconnected handles fail before any outbound work starts.
16
- */
17
-
18
- import {
19
- credentialKey,
20
- type OAuthConnectionRecord,
21
- type StaticCredentialRecord,
22
- StaticCredentialMetadataStore,
23
- } from "@vellumai/credential-storage";
24
- import {
25
- HandleType,
26
- parseHandle,
27
- type LocalOAuthHandle,
28
- type LocalStaticHandle,
29
- } from "@vellumai/service-contracts/credential-rpc";
30
-
31
- // ---------------------------------------------------------------------------
32
- // Resolved subject types
33
- // ---------------------------------------------------------------------------
34
-
35
- /**
36
- * A resolved local static credential subject. Contains the metadata record
37
- * and the secure-key storage key needed to materialise the secret value.
38
- */
39
- export interface ResolvedStaticSubject {
40
- type: typeof HandleType.LocalStatic;
41
- /** The parsed handle. */
42
- handle: LocalStaticHandle;
43
- /** Non-secret metadata record from the credential store. */
44
- metadata: StaticCredentialRecord;
45
- /** Secure-key path where the secret value is stored. */
46
- storageKey: string;
47
- }
48
-
49
- /**
50
- * A resolved local OAuth credential subject. Contains the connection record
51
- * and the connection ID needed to materialise the access token.
52
- */
53
- export interface ResolvedOAuthSubject {
54
- type: typeof HandleType.LocalOAuth;
55
- /** The parsed handle. */
56
- handle: LocalOAuthHandle;
57
- /** OAuth connection record from local persistence. */
58
- connection: OAuthConnectionRecord;
59
- }
60
-
61
- export type ResolvedLocalSubject =
62
- | ResolvedStaticSubject
63
- | ResolvedOAuthSubject;
64
-
65
- // ---------------------------------------------------------------------------
66
- // Resolution result
67
- // ---------------------------------------------------------------------------
68
-
69
- export type SubjectResolutionResult =
70
- | { ok: true; subject: ResolvedLocalSubject }
71
- | { ok: false; error: string };
72
-
73
- // ---------------------------------------------------------------------------
74
- // OAuth connection lookup abstraction
75
- // ---------------------------------------------------------------------------
76
-
77
- /**
78
- * Abstraction for looking up local OAuth connection records.
79
- *
80
- * CES does not import the assistant's SQLite-backed oauth-store. Instead,
81
- * callers provide a lightweight lookup interface that can be backed by
82
- * any persistence mechanism (JSON file, SQLite, in-memory map).
83
- */
84
- export interface OAuthConnectionLookup {
85
- /** Look up a connection by its ID. Returns undefined if not found. */
86
- getById(connectionId: string): OAuthConnectionRecord | undefined;
87
- }
88
-
89
- // ---------------------------------------------------------------------------
90
- // Local subject resolver
91
- // ---------------------------------------------------------------------------
92
-
93
- export interface LocalSubjectResolverDeps {
94
- /** Metadata store for local static credentials. */
95
- metadataStore: StaticCredentialMetadataStore;
96
- /** Lookup for local OAuth connections. */
97
- oauthConnections: OAuthConnectionLookup;
98
- }
99
-
100
- /**
101
- * Resolve a CES credential handle to a local subject.
102
- *
103
- * Supports `local_static` and `local_oauth` handle types. Returns a
104
- * discriminated result so callers can inspect errors without catching
105
- * exceptions.
106
- *
107
- * Resolution is fail-closed: unknown handle types, missing metadata,
108
- * and disconnected OAuth connections all return errors before any
109
- * outbound work starts.
110
- */
111
- export function resolveLocalSubject(
112
- rawHandle: string,
113
- deps: LocalSubjectResolverDeps,
114
- ): SubjectResolutionResult {
115
- const parseResult = parseHandle(rawHandle);
116
- if (!parseResult.ok) {
117
- return { ok: false, error: parseResult.error };
118
- }
119
-
120
- const parsed = parseResult.handle;
121
-
122
- switch (parsed.type) {
123
- case HandleType.LocalStatic: {
124
- const metadata = deps.metadataStore.getByServiceField(
125
- parsed.service,
126
- parsed.field,
127
- );
128
- if (!metadata) {
129
- return {
130
- ok: false,
131
- error: `No local static credential found for service="${parsed.service}", field="${parsed.field}"`,
132
- };
133
- }
134
- const storageKey = credentialKey(parsed.service, parsed.field);
135
- return {
136
- ok: true,
137
- subject: {
138
- type: HandleType.LocalStatic,
139
- handle: parsed,
140
- metadata,
141
- storageKey,
142
- },
143
- };
144
- }
145
-
146
- case HandleType.LocalOAuth: {
147
- const connection = deps.oauthConnections.getById(parsed.connectionId);
148
- if (!connection) {
149
- return {
150
- ok: false,
151
- error: `No local OAuth connection found for connectionId="${parsed.connectionId}"`,
152
- };
153
- }
154
- // Verify the provider key matches the connection's provider
155
- if (connection.providerKey !== parsed.providerKey) {
156
- return {
157
- ok: false,
158
- error: `OAuth connection "${parsed.connectionId}" has providerKey="${connection.providerKey}" but handle specifies "${parsed.providerKey}"`,
159
- };
160
- }
161
- return {
162
- ok: true,
163
- subject: {
164
- type: HandleType.LocalOAuth,
165
- handle: parsed,
166
- connection,
167
- },
168
- };
169
- }
170
-
171
- default:
172
- return {
173
- ok: false,
174
- error: `Handle type "${parsed.type}" is not a local handle and cannot be resolved by the local subject resolver`,
175
- };
176
- }
177
- }