@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,289 +0,0 @@
1
- /**
2
- * CES in-memory temporary grant store.
3
- *
4
- * Manages grants for `allow_once`, `allow_10m`, and `allow_conversation` decisions.
5
- * All state is in-memory — temporary grants never survive a process restart,
6
- * which is the desired behaviour for ephemeral approvals.
7
- *
8
- * Keying:
9
- * - `allow_once`: Keyed by proposal hash. Consumed (deleted) on first use, and
10
- * bounded by a short default TTL so an approval that is never consumed cannot
11
- * linger and be replayed long after the guardian approved the imminent
12
- * operation (see ATL-935).
13
- * - `allow_10m`: Keyed by proposal hash. Checked for expiry on every read;
14
- * expired entries are lazily purged.
15
- * - `allow_conversation`: Keyed by proposal hash + conversation ID. Scoped to a
16
- * single conversation and bounded by a generous absolute TTL backstop so an
17
- * approval cannot live for the store's entire (process-long) lifetime and be
18
- * replayed by a later connection that presents the same conversation ID
19
- * (see ATL-935).
20
- *
21
- * Lifetime note: in managed mode this store instance is process-scoped and
22
- * deliberately shared across assistant reconnects — and, in the forthcoming
23
- * multi-process daemon model, across the multiple connections that each talk to
24
- * CES — so a single guardian approval can be used by any connection entitled to
25
- * it. Grant lifetime is therefore bounded by per-grant TTLs rather than by
26
- * connection teardown: every grant kind carries an expiry, so an unconsumed
27
- * approval expires on its own instead of surviving indefinitely. (A future
28
- * multi-connection daemon may additionally evict on quiescence — when the count
29
- * of live CES connections reaches zero — to scope grants to assistant presence;
30
- * that is connection-lifecycle machinery the multi-connection work should own,
31
- * and is intentionally not built here.)
32
- */
33
-
34
- // ---------------------------------------------------------------------------
35
- // Types
36
- // ---------------------------------------------------------------------------
37
-
38
- export type TemporaryGrantKind = "allow_once" | "allow_10m" | "allow_conversation";
39
-
40
- export interface TemporaryGrant {
41
- /** The kind of temporary grant. */
42
- kind: TemporaryGrantKind;
43
- /** Canonical proposal hash identifying the operation being granted. */
44
- proposalHash: string;
45
- /** Conversation ID — required for `allow_conversation`, ignored otherwise. */
46
- conversationId?: string;
47
- /** When the grant was created (epoch ms). */
48
- createdAt: number;
49
- /** When the grant expires (epoch ms). Set for every grant kind: a short
50
- * default for `allow_once`, the timed window for `allow_10m`, and a generous
51
- * absolute backstop for `allow_conversation`. */
52
- expiresAt?: number;
53
- }
54
-
55
- /** Default TTL for timed grants (10 minutes). */
56
- const DEFAULT_TIMED_DURATION_MS = 10 * 60 * 1000;
57
-
58
- /**
59
- * Default TTL for single-use (`allow_once`) grants (2 minutes).
60
- *
61
- * `allow_once` exists to bridge the gap between a guardian approval and the
62
- * caller immediately retrying the just-approved operation. Without a TTL, an
63
- * approval that is never consumed (e.g. the assistant connection drops before
64
- * the retry) would live for the store's entire lifetime and could later be
65
- * replayed without a fresh prompt (ATL-935). A short bound keeps the grant
66
- * usable for a prompt retry while ensuring a stale, unconsumed approval
67
- * expires on its own.
68
- */
69
- const DEFAULT_ONCE_DURATION_MS = 2 * 60 * 1000;
70
-
71
- /**
72
- * Absolute TTL backstop for `allow_conversation` grants (12 hours).
73
- *
74
- * `allow_conversation` is scoped to a conversation ID and is meant to persist
75
- * for the life of that conversation, so it is not consumed on use and has no
76
- * short timeout. But without any bound it would live for the store's entire
77
- * process-long lifetime and could be replayed by a later connection that
78
- * presents the same conversation ID long after the original approval (ATL-935).
79
- * A generous backstop keeps the grant usable across a normal working session
80
- * while ensuring a long-stale approval eventually requires a fresh prompt.
81
- */
82
- const DEFAULT_CONVERSATION_DURATION_MS = 12 * 60 * 60 * 1000;
83
-
84
- // ---------------------------------------------------------------------------
85
- // Store implementation
86
- // ---------------------------------------------------------------------------
87
-
88
- /**
89
- * Compute the storage key for a temporary grant.
90
- *
91
- * - `allow_once` / `allow_10m`: keyed by proposal hash alone.
92
- * - `allow_conversation`: keyed by proposal hash + conversation ID.
93
- */
94
- function storageKey(
95
- kind: TemporaryGrantKind,
96
- proposalHash: string,
97
- conversationId?: string,
98
- ): string {
99
- if (kind === "allow_conversation") {
100
- if (!conversationId) {
101
- throw new Error(
102
- "allow_conversation grants require a conversationId",
103
- );
104
- }
105
- return `conversation:${conversationId}:${proposalHash}`;
106
- }
107
- return `${kind}:${proposalHash}`;
108
- }
109
-
110
- export class TemporaryGrantStore {
111
- private readonly store = new Map<string, TemporaryGrant>();
112
-
113
- // -----------------------------------------------------------------------
114
- // Public API
115
- // -----------------------------------------------------------------------
116
-
117
- /**
118
- * Record a temporary grant.
119
- *
120
- * For `allow_once` and `allow_10m`, if a grant with the same proposal
121
- * hash already exists, it is replaced (last-write-wins).
122
- *
123
- * @param kind - The type of temporary grant.
124
- * @param proposalHash - Canonical hash of the operation proposal.
125
- * @param options - Additional options (conversationId for conversation grants,
126
- * custom duration for timed grants).
127
- */
128
- add(
129
- kind: TemporaryGrantKind,
130
- proposalHash: string,
131
- options?: {
132
- conversationId?: string;
133
- durationMs?: number;
134
- },
135
- ): void {
136
- const key = storageKey(kind, proposalHash, options?.conversationId);
137
-
138
- const grant: TemporaryGrant = {
139
- kind,
140
- proposalHash,
141
- createdAt: Date.now(),
142
- };
143
-
144
- if (options?.conversationId) {
145
- grant.conversationId = options.conversationId;
146
- }
147
-
148
- if (kind === "allow_10m") {
149
- grant.expiresAt =
150
- Date.now() + (options?.durationMs ?? DEFAULT_TIMED_DURATION_MS);
151
- } else if (kind === "allow_once") {
152
- // `allow_once` is always bounded by a TTL — a caller-supplied duration
153
- // when present, otherwise a short default — so an unconsumed single-use
154
- // approval cannot be replayed indefinitely (ATL-935).
155
- grant.expiresAt =
156
- Date.now() + (options?.durationMs ?? DEFAULT_ONCE_DURATION_MS);
157
- } else if (kind === "allow_conversation") {
158
- // `allow_conversation` persists for the conversation and is not consumed
159
- // on use, but it still carries a generous absolute TTL backstop so a
160
- // conversation-scoped approval cannot linger for the store's entire
161
- // process lifetime and be replayed by a later connection (ATL-935).
162
- grant.expiresAt =
163
- Date.now() + (options?.durationMs ?? DEFAULT_CONVERSATION_DURATION_MS);
164
- }
165
-
166
- this.store.set(key, grant);
167
- }
168
-
169
- /**
170
- * Check whether an active temporary grant exists for the given proposal.
171
- *
172
- * - `allow_once`: Returns `true` and **consumes** the grant (deletes it).
173
- * - `allow_10m`: Returns `true` only if the grant has not expired.
174
- * Expired grants are lazily purged.
175
- * - `allow_conversation`: Returns `true` only if a non-expired grant exists
176
- * for the given proposal hash scoped to the specified conversation ID.
177
- * Expired grants (past the absolute TTL backstop) are lazily purged.
178
- *
179
- * Returns `false` if no matching grant exists.
180
- */
181
- check(
182
- kind: TemporaryGrantKind,
183
- proposalHash: string,
184
- conversationId?: string,
185
- ): boolean {
186
- const key = storageKey(kind, proposalHash, conversationId);
187
- const grant = this.store.get(key);
188
- if (!grant) return false;
189
-
190
- if (grant.kind === "allow_once") {
191
- // Check TTL if set
192
- if (grant.expiresAt !== undefined && Date.now() >= grant.expiresAt) {
193
- this.store.delete(key);
194
- return false;
195
- }
196
- // Consume on first use
197
- this.store.delete(key);
198
- return true;
199
- }
200
-
201
- if (grant.kind === "allow_10m") {
202
- if (grant.expiresAt !== undefined && Date.now() >= grant.expiresAt) {
203
- // Expired — purge and deny
204
- this.store.delete(key);
205
- return false;
206
- }
207
- return true;
208
- }
209
-
210
- // allow_conversation — bounded by an absolute TTL backstop; lazily purge an
211
- // expired grant and deny, mirroring allow_10m.
212
- if (grant.expiresAt !== undefined && Date.now() >= grant.expiresAt) {
213
- this.store.delete(key);
214
- return false;
215
- }
216
- return true;
217
- }
218
-
219
- /**
220
- * Check whether any kind of active temporary grant exists for the given
221
- * proposal hash and optional conversation ID.
222
- *
223
- * Checks `allow_once`, `allow_10m`, and `allow_conversation` in order.
224
- * Returns the kind of the matched grant, or `undefined` if none match.
225
- *
226
- * Note: If an `allow_once` grant matches, it is consumed.
227
- */
228
- checkAny(
229
- proposalHash: string,
230
- conversationId?: string,
231
- ): TemporaryGrantKind | undefined {
232
- // Check allow_once first (most specific / single-use)
233
- if (this.check("allow_once", proposalHash)) return "allow_once";
234
-
235
- // Check allow_10m
236
- if (this.check("allow_10m", proposalHash)) return "allow_10m";
237
-
238
- // Check allow_conversation (requires conversationId)
239
- if (conversationId && this.check("allow_conversation", proposalHash, conversationId)) {
240
- return "allow_conversation";
241
- }
242
-
243
- return undefined;
244
- }
245
-
246
- /**
247
- * Remove a specific temporary grant.
248
- *
249
- * Returns `true` if the grant existed and was removed.
250
- */
251
- remove(
252
- kind: TemporaryGrantKind,
253
- proposalHash: string,
254
- conversationId?: string,
255
- ): boolean {
256
- const key = storageKey(kind, proposalHash, conversationId);
257
- return this.store.delete(key);
258
- }
259
-
260
- /**
261
- * Remove all temporary grants for a given conversation ID.
262
- *
263
- * Useful when a conversation ends. Only removes `allow_conversation`
264
- * grants scoped to that conversation.
265
- */
266
- clearConversation(conversationId: string): void {
267
- const prefix = `conversation:${conversationId}:`;
268
- for (const key of this.store.keys()) {
269
- if (key.startsWith(prefix)) {
270
- this.store.delete(key);
271
- }
272
- }
273
- }
274
-
275
- /**
276
- * Remove all temporary grants. Useful for testing or full reset.
277
- */
278
- clear(): void {
279
- this.store.clear();
280
- }
281
-
282
- /**
283
- * Return the number of currently stored grants (including expired ones
284
- * that haven't been lazily purged yet).
285
- */
286
- get size(): number {
287
- return this.store.size;
288
- }
289
- }
package/src/http/audit.ts DELETED
@@ -1,84 +0,0 @@
1
- /**
2
- * HTTP audit summary generation for the Credential Execution Service.
3
- *
4
- * Produces token-free audit summaries of credentialed HTTP operations.
5
- * These summaries are stored in the CES audit log and may be exposed
6
- * to the assistant runtime for observability — they must never contain
7
- * secret values, auth tokens, or raw credential material.
8
- *
9
- * Audit summaries capture:
10
- * - What was accessed (method, URL template, status code)
11
- * - Which credential and grant were used
12
- * - Whether the operation succeeded
13
- * - Timing metadata
14
- */
15
-
16
- import { randomUUID } from "node:crypto";
17
-
18
- import type { AuditRecordSummary } from "@vellumai/service-contracts/credential-rpc";
19
- import { derivePathTemplate } from "./path-template.js";
20
-
21
- // ---------------------------------------------------------------------------
22
- // Types
23
- // ---------------------------------------------------------------------------
24
-
25
- export interface HttpAuditInput {
26
- /** CES credential handle used for this request. */
27
- credentialHandle: string;
28
- /** Grant ID that authorised this request. */
29
- grantId: string;
30
- /** CES session ID. */
31
- sessionId: string;
32
- /** HTTP method. */
33
- method: string;
34
- /** Raw target URL (will be templated for the audit record). */
35
- url: string;
36
- /** Whether the HTTP operation succeeded. */
37
- success: boolean;
38
- /** HTTP status code (if available). */
39
- statusCode?: number;
40
- /** Error message if the operation failed (must not contain secrets). */
41
- errorMessage?: string;
42
- }
43
-
44
- // ---------------------------------------------------------------------------
45
- // Summary generation
46
- // ---------------------------------------------------------------------------
47
-
48
- /**
49
- * Generate a token-free audit record summary for an HTTP operation.
50
- *
51
- * The `target` field uses the path template (with placeholders) rather
52
- * than the raw URL to avoid leaking path-level identifiers that might
53
- * be sensitive (e.g. personal resource IDs). The method is prepended
54
- * for readability: `GET https://api.example.com/users/{:num}`.
55
- */
56
- export function generateHttpAuditSummary(
57
- input: HttpAuditInput,
58
- ): AuditRecordSummary {
59
- let target: string;
60
- try {
61
- const template = derivePathTemplate(input.url);
62
- target = `${input.method.toUpperCase()} ${template}`;
63
- } catch {
64
- // If URL parsing fails, use a safe redacted placeholder
65
- target = `${input.method.toUpperCase()} [invalid-url]`;
66
- }
67
-
68
- // Append status code if available
69
- if (input.statusCode !== undefined) {
70
- target += ` -> ${input.statusCode}`;
71
- }
72
-
73
- return {
74
- auditId: randomUUID(),
75
- grantId: input.grantId,
76
- credentialHandle: input.credentialHandle,
77
- toolName: "http",
78
- target,
79
- sessionId: input.sessionId,
80
- success: input.success,
81
- ...(input.errorMessage ? { errorMessage: input.errorMessage } : {}),
82
- timestamp: new Date().toISOString(),
83
- };
84
- }