@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.
- package/Dockerfile +1 -1
- package/node_modules/@vellumai/service-contracts/package.json +1 -2
- package/node_modules/@vellumai/service-contracts/src/__tests__/attachment-naming.test.ts +104 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/contracts.test.ts +0 -2
- package/node_modules/@vellumai/service-contracts/src/attachment-naming.ts +118 -0
- package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +3 -5
- package/node_modules/@vellumai/service-contracts/src/index.ts +2 -4
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +4 -447
- package/package.json +2 -3
- package/src/__tests__/bulk-set-credentials.test.ts +1 -1
- package/src/__tests__/local-standalone.test.ts +5 -36
- package/src/__tests__/managed-integration.test.ts +112 -91
- package/src/__tests__/managed-reconnect.test.ts +2 -2
- package/src/__tests__/transport.test.ts +23 -27
- package/src/cli.ts +1 -1
- package/src/index.ts +8 -88
- package/src/main.ts +228 -340
- package/src/paths.ts +4 -20
- package/src/server.ts +52 -469
- package/node_modules/@vellumai/service-contracts/src/__tests__/grants.test.ts +0 -686
- package/node_modules/@vellumai/service-contracts/src/grants.ts +0 -184
- package/node_modules/@vellumai/service-contracts/src/rendering.ts +0 -135
- package/src/__tests__/command-executor.test.ts +0 -1879
- package/src/__tests__/command-validator.test.ts +0 -1405
- package/src/__tests__/command-workspace.test.ts +0 -1050
- package/src/__tests__/grant-store.test.ts +0 -689
- package/src/__tests__/http-executor.test.ts +0 -1336
- package/src/__tests__/http-policy.test.ts +0 -1069
- package/src/__tests__/local-materializers.test.ts +0 -860
- package/src/__tests__/local-token-refresh.test.ts +0 -361
- package/src/__tests__/manage-secure-command-tool.test.ts +0 -134
- package/src/__tests__/managed-lazy-getters.test.ts +0 -359
- package/src/__tests__/managed-materializers.test.ts +0 -1028
- package/src/__tests__/managed-rejection.test.ts +0 -43
- package/src/__tests__/toolstore.test.ts +0 -773
- package/src/audit/store.ts +0 -188
- package/src/commands/auth-adapters.ts +0 -169
- package/src/commands/egress-hooks.ts +0 -203
- package/src/commands/executor.ts +0 -1155
- package/src/commands/output-scan.ts +0 -157
- package/src/commands/profiles.ts +0 -286
- package/src/commands/validator.ts +0 -702
- package/src/commands/workspace.ts +0 -550
- package/src/grants/index.ts +0 -17
- package/src/grants/persistent-store.ts +0 -309
- package/src/grants/rpc-handlers.ts +0 -293
- package/src/grants/temporary-store.ts +0 -289
- package/src/http/audit.ts +0 -84
- package/src/http/executor.ts +0 -684
- package/src/http/path-template.ts +0 -245
- package/src/http/policy.ts +0 -238
- package/src/http/response-filter.ts +0 -233
- package/src/managed-errors.ts +0 -9
- package/src/managed-lazy-getters.ts +0 -106
- package/src/managed-main.ts +0 -822
- package/src/materializers/local-oauth-lookup.ts +0 -98
- package/src/materializers/local-token-refresh.ts +0 -287
- package/src/materializers/local.ts +0 -316
- package/src/materializers/managed-platform.ts +0 -295
- package/src/subjects/local.ts +0 -177
- package/src/subjects/managed.ts +0 -311
- package/src/subjects/policy.ts +0 -79
- package/src/toolstore/integrity.ts +0 -94
- package/src/toolstore/manifest.ts +0 -154
- 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
|
-
}
|