@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,686 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for CES grants, proposals, handles, rendering, and RPC contracts.
|
|
3
|
-
*
|
|
4
|
-
* Covers:
|
|
5
|
-
* 1. Handle parsing and construction roundtrips.
|
|
6
|
-
* 2. Grant proposal schema validation.
|
|
7
|
-
* 3. Proposal hash determinism — same content, different key order → same hash.
|
|
8
|
-
* 4. Proposal rendering produces stable, human-readable output.
|
|
9
|
-
* 5. Persistent grant record and audit record schema validation.
|
|
10
|
-
* 6. RPC schema validation for all methods.
|
|
11
|
-
* 7. No raw secret-return fields in any schema.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, expect, test } from "bun:test";
|
|
15
|
-
import {
|
|
16
|
-
AuditRecordSummarySchema,
|
|
17
|
-
CommandGrantProposalSchema,
|
|
18
|
-
GrantProposalSchema,
|
|
19
|
-
HttpGrantProposalSchema,
|
|
20
|
-
PersistentGrantRecordSchema,
|
|
21
|
-
TemporaryGrantDecisionSchema,
|
|
22
|
-
} from "../grants.js";
|
|
23
|
-
import {
|
|
24
|
-
HandleType,
|
|
25
|
-
localOAuthHandle,
|
|
26
|
-
localStaticHandle,
|
|
27
|
-
parseHandle,
|
|
28
|
-
platformOAuthHandle,
|
|
29
|
-
CredentialHandleSchema,
|
|
30
|
-
} from "../handles.js";
|
|
31
|
-
import { canonicalJsonSerialize, hashProposal, renderProposal } from "../rendering.js";
|
|
32
|
-
import {
|
|
33
|
-
ApprovalRequiredSchema,
|
|
34
|
-
CesRpcMethod,
|
|
35
|
-
CesRpcSchemas,
|
|
36
|
-
ListAuditRecordsSchema,
|
|
37
|
-
ListGrantsSchema,
|
|
38
|
-
MakeAuthenticatedRequestResponseSchema,
|
|
39
|
-
MakeAuthenticatedRequestSchema,
|
|
40
|
-
ManageSecureCommandToolSchema,
|
|
41
|
-
RecordGrantSchema,
|
|
42
|
-
RevokeGrantSchema,
|
|
43
|
-
RunAuthenticatedCommandResponseSchema,
|
|
44
|
-
RunAuthenticatedCommandSchema,
|
|
45
|
-
} from "../rpc.js";
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Handle parsing and construction
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
describe("handles", () => {
|
|
52
|
-
describe("localStaticHandle", () => {
|
|
53
|
-
test("constructs the expected format", () => {
|
|
54
|
-
expect(localStaticHandle("github", "api_key")).toBe(
|
|
55
|
-
"local_static:github/api_key",
|
|
56
|
-
);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("roundtrips through parseHandle", () => {
|
|
60
|
-
const raw = localStaticHandle("fal", "password");
|
|
61
|
-
const result = parseHandle(raw);
|
|
62
|
-
expect(result.ok).toBe(true);
|
|
63
|
-
if (!result.ok) return;
|
|
64
|
-
expect(result.handle.type).toBe(HandleType.LocalStatic);
|
|
65
|
-
if (result.handle.type !== HandleType.LocalStatic) return;
|
|
66
|
-
expect(result.handle.service).toBe("fal");
|
|
67
|
-
expect(result.handle.field).toBe("password");
|
|
68
|
-
expect(result.handle.raw).toBe(raw);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe("localOAuthHandle", () => {
|
|
73
|
-
test("constructs the expected format", () => {
|
|
74
|
-
expect(localOAuthHandle("google", "conn-123")).toBe(
|
|
75
|
-
"local_oauth:google/conn-123",
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("roundtrips through parseHandle", () => {
|
|
80
|
-
const raw = localOAuthHandle("slack", "conn-abc");
|
|
81
|
-
const result = parseHandle(raw);
|
|
82
|
-
expect(result.ok).toBe(true);
|
|
83
|
-
if (!result.ok) return;
|
|
84
|
-
expect(result.handle.type).toBe(HandleType.LocalOAuth);
|
|
85
|
-
if (result.handle.type !== HandleType.LocalOAuth) return;
|
|
86
|
-
expect(result.handle.providerKey).toBe("slack");
|
|
87
|
-
expect(result.handle.connectionId).toBe("conn-abc");
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
describe("platformOAuthHandle", () => {
|
|
92
|
-
test("constructs the expected format", () => {
|
|
93
|
-
expect(platformOAuthHandle("plat-conn-456")).toBe(
|
|
94
|
-
"platform_oauth:plat-conn-456",
|
|
95
|
-
);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("roundtrips through parseHandle", () => {
|
|
99
|
-
const raw = platformOAuthHandle("plat-conn-789");
|
|
100
|
-
const result = parseHandle(raw);
|
|
101
|
-
expect(result.ok).toBe(true);
|
|
102
|
-
if (!result.ok) return;
|
|
103
|
-
expect(result.handle.type).toBe(HandleType.PlatformOAuth);
|
|
104
|
-
if (result.handle.type !== HandleType.PlatformOAuth) return;
|
|
105
|
-
expect(result.handle.connectionId).toBe("plat-conn-789");
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe("parseHandle error cases", () => {
|
|
110
|
-
test("rejects a handle with no colon", () => {
|
|
111
|
-
const result = parseHandle("no-prefix");
|
|
112
|
-
expect(result.ok).toBe(false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("rejects an unknown prefix", () => {
|
|
116
|
-
const result = parseHandle("cloud_kms:some-key");
|
|
117
|
-
expect(result.ok).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("rejects local_static with no slash", () => {
|
|
121
|
-
const result = parseHandle("local_static:nofield");
|
|
122
|
-
expect(result.ok).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("rejects local_static with empty service", () => {
|
|
126
|
-
const result = parseHandle("local_static:/field");
|
|
127
|
-
expect(result.ok).toBe(false);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("rejects local_static with empty field", () => {
|
|
131
|
-
const result = parseHandle("local_static:service/");
|
|
132
|
-
expect(result.ok).toBe(false);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("rejects local_oauth with no slash", () => {
|
|
136
|
-
const result = parseHandle("local_oauth:google");
|
|
137
|
-
expect(result.ok).toBe(false);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("rejects platform_oauth with empty connectionId", () => {
|
|
141
|
-
const result = parseHandle("platform_oauth:");
|
|
142
|
-
expect(result.ok).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("CredentialHandleSchema", () => {
|
|
147
|
-
test("accepts valid handles", () => {
|
|
148
|
-
expect(() =>
|
|
149
|
-
CredentialHandleSchema.parse("local_static:github/api_key"),
|
|
150
|
-
).not.toThrow();
|
|
151
|
-
expect(() =>
|
|
152
|
-
CredentialHandleSchema.parse(
|
|
153
|
-
"local_oauth:google/conn-1",
|
|
154
|
-
),
|
|
155
|
-
).not.toThrow();
|
|
156
|
-
expect(() =>
|
|
157
|
-
CredentialHandleSchema.parse("platform_oauth:conn-2"),
|
|
158
|
-
).not.toThrow();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("rejects invalid handles", () => {
|
|
162
|
-
expect(() => CredentialHandleSchema.parse("bad-handle")).toThrow();
|
|
163
|
-
});
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// ---------------------------------------------------------------------------
|
|
168
|
-
// Grant proposal schemas
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
|
|
171
|
-
describe("grant proposals", () => {
|
|
172
|
-
const httpProposal = {
|
|
173
|
-
type: "http" as const,
|
|
174
|
-
credentialHandle: "local_static:github/api_key",
|
|
175
|
-
method: "GET",
|
|
176
|
-
url: "https://api.github.com/repos",
|
|
177
|
-
purpose: "List repositories",
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const commandProposal = {
|
|
181
|
-
type: "command" as const,
|
|
182
|
-
credentialHandle: "local_static:aws/access_key",
|
|
183
|
-
command: "aws s3 ls",
|
|
184
|
-
purpose: "List S3 buckets",
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
test("HttpGrantProposalSchema parses valid proposal", () => {
|
|
188
|
-
const result = HttpGrantProposalSchema.parse(httpProposal);
|
|
189
|
-
expect(result.type).toBe("http");
|
|
190
|
-
expect(result.method).toBe("GET");
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
test("HttpGrantProposalSchema accepts optional allowedUrlPatterns", () => {
|
|
194
|
-
const result = HttpGrantProposalSchema.parse({
|
|
195
|
-
...httpProposal,
|
|
196
|
-
allowedUrlPatterns: ["https://api.github.com/**"],
|
|
197
|
-
});
|
|
198
|
-
expect(result.allowedUrlPatterns).toEqual(["https://api.github.com/**"]);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("CommandGrantProposalSchema parses valid proposal", () => {
|
|
202
|
-
const result = CommandGrantProposalSchema.parse(commandProposal);
|
|
203
|
-
expect(result.type).toBe("command");
|
|
204
|
-
expect(result.command).toBe("aws s3 ls");
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("GrantProposalSchema discriminates by type", () => {
|
|
208
|
-
const http = GrantProposalSchema.parse(httpProposal);
|
|
209
|
-
expect(http.type).toBe("http");
|
|
210
|
-
|
|
211
|
-
const cmd = GrantProposalSchema.parse(commandProposal);
|
|
212
|
-
expect(cmd.type).toBe("command");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("GrantProposalSchema rejects unknown type", () => {
|
|
216
|
-
expect(() =>
|
|
217
|
-
GrantProposalSchema.parse({
|
|
218
|
-
type: "browser_fill",
|
|
219
|
-
credentialHandle: "local_static:x/y",
|
|
220
|
-
purpose: "test",
|
|
221
|
-
}),
|
|
222
|
-
).toThrow();
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// Proposal hash determinism
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
describe("proposal hashing", () => {
|
|
231
|
-
test("same proposal produces same hash", () => {
|
|
232
|
-
const proposal = {
|
|
233
|
-
type: "http" as const,
|
|
234
|
-
credentialHandle: "local_static:github/api_key",
|
|
235
|
-
method: "POST",
|
|
236
|
-
url: "https://api.github.com/repos",
|
|
237
|
-
purpose: "Create repository",
|
|
238
|
-
};
|
|
239
|
-
const hash1 = hashProposal(proposal);
|
|
240
|
-
const hash2 = hashProposal(proposal);
|
|
241
|
-
expect(hash1).toBe(hash2);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
test("different key order produces same hash", () => {
|
|
245
|
-
const proposal1 = {
|
|
246
|
-
type: "http" as const,
|
|
247
|
-
credentialHandle: "local_static:github/api_key",
|
|
248
|
-
method: "GET",
|
|
249
|
-
url: "https://api.github.com/repos",
|
|
250
|
-
purpose: "List repos",
|
|
251
|
-
};
|
|
252
|
-
// Same content, different key ordering
|
|
253
|
-
const proposal2 = {
|
|
254
|
-
purpose: "List repos",
|
|
255
|
-
url: "https://api.github.com/repos",
|
|
256
|
-
method: "GET",
|
|
257
|
-
type: "http" as const,
|
|
258
|
-
credentialHandle: "local_static:github/api_key",
|
|
259
|
-
};
|
|
260
|
-
expect(hashProposal(proposal1)).toBe(hashProposal(proposal2));
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
test("different proposals produce different hashes", () => {
|
|
264
|
-
const proposal1 = {
|
|
265
|
-
type: "http" as const,
|
|
266
|
-
credentialHandle: "local_static:github/api_key",
|
|
267
|
-
method: "GET",
|
|
268
|
-
url: "https://api.github.com/repos",
|
|
269
|
-
purpose: "List repos",
|
|
270
|
-
};
|
|
271
|
-
const proposal2 = {
|
|
272
|
-
type: "http" as const,
|
|
273
|
-
credentialHandle: "local_static:github/api_key",
|
|
274
|
-
method: "POST",
|
|
275
|
-
url: "https://api.github.com/repos",
|
|
276
|
-
purpose: "Create repo",
|
|
277
|
-
};
|
|
278
|
-
expect(hashProposal(proposal1)).not.toBe(hashProposal(proposal2));
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
test("hash is a 64-char lowercase hex string (SHA-256)", () => {
|
|
282
|
-
const hash = hashProposal({
|
|
283
|
-
type: "command" as const,
|
|
284
|
-
credentialHandle: "local_static:aws/key",
|
|
285
|
-
command: "aws s3 ls",
|
|
286
|
-
purpose: "test",
|
|
287
|
-
});
|
|
288
|
-
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
289
|
-
});
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
// ---------------------------------------------------------------------------
|
|
293
|
-
// Canonical JSON serialization
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
|
-
|
|
296
|
-
describe("canonicalJsonSerialize", () => {
|
|
297
|
-
test("sorts keys recursively", () => {
|
|
298
|
-
const result = canonicalJsonSerialize({ z: 1, a: { c: 3, b: 2 } });
|
|
299
|
-
expect(result).toBe('{"a":{"b":2,"c":3},"z":1}');
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
test("preserves array order", () => {
|
|
303
|
-
const result = canonicalJsonSerialize({ items: [3, 1, 2] });
|
|
304
|
-
expect(result).toBe('{"items":[3,1,2]}');
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("preserves null", () => {
|
|
308
|
-
const result = canonicalJsonSerialize({ a: null });
|
|
309
|
-
expect(result).toBe('{"a":null}');
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
// ---------------------------------------------------------------------------
|
|
314
|
-
// Proposal rendering
|
|
315
|
-
// ---------------------------------------------------------------------------
|
|
316
|
-
|
|
317
|
-
describe("renderProposal", () => {
|
|
318
|
-
test("renders HTTP proposal with expected format", () => {
|
|
319
|
-
const rendered = renderProposal({
|
|
320
|
-
type: "http",
|
|
321
|
-
credentialHandle: "local_static:github/api_key",
|
|
322
|
-
method: "GET",
|
|
323
|
-
url: "https://api.github.com/repos",
|
|
324
|
-
purpose: "List repositories",
|
|
325
|
-
});
|
|
326
|
-
expect(rendered).toContain("Authenticated HTTP Request");
|
|
327
|
-
expect(rendered).toContain("Method: GET");
|
|
328
|
-
expect(rendered).toContain("URL: https://api.github.com/repos");
|
|
329
|
-
expect(rendered).toContain("Credential: local_static:github/api_key");
|
|
330
|
-
expect(rendered).toContain("Purpose: List repositories");
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test("renders HTTP proposal with URL patterns", () => {
|
|
334
|
-
const rendered = renderProposal({
|
|
335
|
-
type: "http",
|
|
336
|
-
credentialHandle: "local_static:github/api_key",
|
|
337
|
-
method: "GET",
|
|
338
|
-
url: "https://api.github.com/repos",
|
|
339
|
-
purpose: "test",
|
|
340
|
-
allowedUrlPatterns: ["https://api.github.com/**"],
|
|
341
|
-
});
|
|
342
|
-
expect(rendered).toContain("Allowed URL patterns:");
|
|
343
|
-
expect(rendered).toContain("- https://api.github.com/**");
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("renders command proposal with expected format", () => {
|
|
347
|
-
const rendered = renderProposal({
|
|
348
|
-
type: "command",
|
|
349
|
-
credentialHandle: "local_static:aws/key",
|
|
350
|
-
command: "aws s3 ls",
|
|
351
|
-
purpose: "List S3 buckets",
|
|
352
|
-
});
|
|
353
|
-
expect(rendered).toContain("Authenticated Command Execution");
|
|
354
|
-
expect(rendered).toContain("Command: aws s3 ls");
|
|
355
|
-
expect(rendered).toContain("Purpose: List S3 buckets");
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
test("rendering is deterministic", () => {
|
|
359
|
-
const proposal = {
|
|
360
|
-
type: "http" as const,
|
|
361
|
-
credentialHandle: "local_static:github/api_key",
|
|
362
|
-
method: "POST",
|
|
363
|
-
url: "https://api.github.com/repos",
|
|
364
|
-
purpose: "Create repo",
|
|
365
|
-
};
|
|
366
|
-
expect(renderProposal(proposal)).toBe(renderProposal(proposal));
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// ---------------------------------------------------------------------------
|
|
371
|
-
// Grant decision and record schemas
|
|
372
|
-
// ---------------------------------------------------------------------------
|
|
373
|
-
|
|
374
|
-
describe("TemporaryGrantDecisionSchema", () => {
|
|
375
|
-
test("parses an approved decision", () => {
|
|
376
|
-
const result = TemporaryGrantDecisionSchema.parse({
|
|
377
|
-
proposal: {
|
|
378
|
-
type: "http",
|
|
379
|
-
credentialHandle: "local_static:github/api_key",
|
|
380
|
-
method: "GET",
|
|
381
|
-
url: "https://api.github.com/repos",
|
|
382
|
-
purpose: "List repos",
|
|
383
|
-
},
|
|
384
|
-
proposalHash: "abcdef1234567890".repeat(4),
|
|
385
|
-
decision: "approved",
|
|
386
|
-
decidedBy: "guardian:user@example.com",
|
|
387
|
-
decidedAt: new Date().toISOString(),
|
|
388
|
-
ttl: "PT1H",
|
|
389
|
-
});
|
|
390
|
-
expect(result.decision).toBe("approved");
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
test("parses a denied decision with reason", () => {
|
|
394
|
-
const result = TemporaryGrantDecisionSchema.parse({
|
|
395
|
-
proposal: {
|
|
396
|
-
type: "command",
|
|
397
|
-
credentialHandle: "local_static:aws/key",
|
|
398
|
-
command: "aws s3 rm --recursive",
|
|
399
|
-
purpose: "Clean up bucket",
|
|
400
|
-
},
|
|
401
|
-
proposalHash: "abcdef1234567890".repeat(4),
|
|
402
|
-
decision: "denied",
|
|
403
|
-
decidedBy: "guardian:admin",
|
|
404
|
-
decidedAt: new Date().toISOString(),
|
|
405
|
-
reason: "Destructive operation not permitted",
|
|
406
|
-
});
|
|
407
|
-
expect(result.decision).toBe("denied");
|
|
408
|
-
expect(result.reason).toBe("Destructive operation not permitted");
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
describe("PersistentGrantRecordSchema", () => {
|
|
413
|
-
test("parses a valid active grant", () => {
|
|
414
|
-
const now = new Date().toISOString();
|
|
415
|
-
const result = PersistentGrantRecordSchema.parse({
|
|
416
|
-
grantId: "grant-001",
|
|
417
|
-
sessionId: "sess-001",
|
|
418
|
-
credentialHandle: "local_static:github/api_key",
|
|
419
|
-
proposalType: "http",
|
|
420
|
-
proposalHash: "abcdef1234567890".repeat(4),
|
|
421
|
-
allowedPurposes: ["https://api.github.com/**"],
|
|
422
|
-
status: "active",
|
|
423
|
-
grantedBy: "guardian:user@example.com",
|
|
424
|
-
createdAt: now,
|
|
425
|
-
expiresAt: null,
|
|
426
|
-
consumedAt: null,
|
|
427
|
-
revokedAt: null,
|
|
428
|
-
});
|
|
429
|
-
expect(result.status).toBe("active");
|
|
430
|
-
expect(result.grantId).toBe("grant-001");
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test("rejects a grant with unknown status", () => {
|
|
434
|
-
expect(() =>
|
|
435
|
-
PersistentGrantRecordSchema.parse({
|
|
436
|
-
grantId: "grant-001",
|
|
437
|
-
sessionId: "sess-001",
|
|
438
|
-
credentialHandle: "local_static:github/api_key",
|
|
439
|
-
proposalType: "http",
|
|
440
|
-
proposalHash: "abc",
|
|
441
|
-
allowedPurposes: [],
|
|
442
|
-
status: "pending",
|
|
443
|
-
grantedBy: "guardian:user",
|
|
444
|
-
createdAt: new Date().toISOString(),
|
|
445
|
-
expiresAt: null,
|
|
446
|
-
consumedAt: null,
|
|
447
|
-
revokedAt: null,
|
|
448
|
-
}),
|
|
449
|
-
).toThrow();
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
describe("AuditRecordSummarySchema", () => {
|
|
454
|
-
test("parses a valid audit record", () => {
|
|
455
|
-
const result = AuditRecordSummarySchema.parse({
|
|
456
|
-
auditId: "audit-001",
|
|
457
|
-
grantId: "grant-001",
|
|
458
|
-
credentialHandle: "local_static:github/api_key",
|
|
459
|
-
toolName: "make_authenticated_request",
|
|
460
|
-
target: "https://api.github.com/repos",
|
|
461
|
-
sessionId: "sess-001",
|
|
462
|
-
success: true,
|
|
463
|
-
timestamp: new Date().toISOString(),
|
|
464
|
-
});
|
|
465
|
-
expect(result.auditId).toBe("audit-001");
|
|
466
|
-
expect(result.success).toBe(true);
|
|
467
|
-
});
|
|
468
|
-
|
|
469
|
-
test("parses a failed audit record with error message", () => {
|
|
470
|
-
const result = AuditRecordSummarySchema.parse({
|
|
471
|
-
auditId: "audit-002",
|
|
472
|
-
grantId: "grant-001",
|
|
473
|
-
credentialHandle: "local_static:github/api_key",
|
|
474
|
-
toolName: "make_authenticated_request",
|
|
475
|
-
target: "https://api.github.com/repos",
|
|
476
|
-
sessionId: "sess-001",
|
|
477
|
-
success: false,
|
|
478
|
-
errorMessage: "Connection timeout",
|
|
479
|
-
timestamp: new Date().toISOString(),
|
|
480
|
-
});
|
|
481
|
-
expect(result.success).toBe(false);
|
|
482
|
-
expect(result.errorMessage).toBe("Connection timeout");
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// ---------------------------------------------------------------------------
|
|
487
|
-
// RPC method schemas
|
|
488
|
-
// ---------------------------------------------------------------------------
|
|
489
|
-
|
|
490
|
-
describe("RPC schemas", () => {
|
|
491
|
-
test("MakeAuthenticatedRequestSchema parses valid request", () => {
|
|
492
|
-
const result = MakeAuthenticatedRequestSchema.parse({
|
|
493
|
-
credentialHandle: "local_static:github/api_key",
|
|
494
|
-
method: "GET",
|
|
495
|
-
url: "https://api.github.com/repos",
|
|
496
|
-
purpose: "List repos",
|
|
497
|
-
});
|
|
498
|
-
expect(result.method).toBe("GET");
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
test("MakeAuthenticatedRequestResponseSchema parses success", () => {
|
|
502
|
-
const result = MakeAuthenticatedRequestResponseSchema.parse({
|
|
503
|
-
success: true,
|
|
504
|
-
statusCode: 200,
|
|
505
|
-
responseBody: '{"data": []}',
|
|
506
|
-
auditId: "audit-001",
|
|
507
|
-
});
|
|
508
|
-
expect(result.success).toBe(true);
|
|
509
|
-
expect(result.statusCode).toBe(200);
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
test("RunAuthenticatedCommandSchema parses valid request", () => {
|
|
513
|
-
const result = RunAuthenticatedCommandSchema.parse({
|
|
514
|
-
credentialHandle: "local_static:aws/key",
|
|
515
|
-
command: "aws s3 ls",
|
|
516
|
-
purpose: "List buckets",
|
|
517
|
-
});
|
|
518
|
-
expect(result.command).toBe("aws s3 ls");
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
test("RunAuthenticatedCommandResponseSchema parses success", () => {
|
|
522
|
-
const result = RunAuthenticatedCommandResponseSchema.parse({
|
|
523
|
-
success: true,
|
|
524
|
-
exitCode: 0,
|
|
525
|
-
stdout: "bucket-1\nbucket-2\n",
|
|
526
|
-
stderr: "",
|
|
527
|
-
auditId: "audit-002",
|
|
528
|
-
});
|
|
529
|
-
expect(result.exitCode).toBe(0);
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
test("ManageSecureCommandToolSchema parses register action", () => {
|
|
533
|
-
const result = ManageSecureCommandToolSchema.parse({
|
|
534
|
-
action: "register",
|
|
535
|
-
toolName: "aws-cli",
|
|
536
|
-
credentialHandle: "local_static:aws/key",
|
|
537
|
-
description: "AWS CLI with credentials",
|
|
538
|
-
bundleId: "aws-cli",
|
|
539
|
-
version: "2.15.0",
|
|
540
|
-
sourceUrl: "https://bundles.example.com/aws-cli-2.15.0.vbundle",
|
|
541
|
-
sha256: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
|
542
|
-
secureCommandManifest: {
|
|
543
|
-
schemaVersion: "1",
|
|
544
|
-
bundleDigest: "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
|
545
|
-
bundleId: "aws-cli",
|
|
546
|
-
version: "2.15.0",
|
|
547
|
-
entrypoint: "bin/aws",
|
|
548
|
-
commandProfiles: {
|
|
549
|
-
aws: {
|
|
550
|
-
description: "AWS CLI access",
|
|
551
|
-
allowedArgvPatterns: [{ name: "any", tokens: ["<cmd...>"] }],
|
|
552
|
-
deniedSubcommands: [],
|
|
553
|
-
},
|
|
554
|
-
},
|
|
555
|
-
authAdapter: { type: "env_var" as const, envVarName: "AWS_ACCESS_KEY_ID" },
|
|
556
|
-
egressMode: "proxy_required" as const,
|
|
557
|
-
},
|
|
558
|
-
});
|
|
559
|
-
expect(result.action).toBe("register");
|
|
560
|
-
expect(result.bundleId).toBe("aws-cli");
|
|
561
|
-
expect(result.version).toBe("2.15.0");
|
|
562
|
-
expect(result.sourceUrl).toBe("https://bundles.example.com/aws-cli-2.15.0.vbundle");
|
|
563
|
-
expect(result.sha256).toBe("abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890");
|
|
564
|
-
expect(result.secureCommandManifest).toBeDefined();
|
|
565
|
-
expect(result.secureCommandManifest!.entrypoint).toBe("bin/aws");
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
test("ManageSecureCommandToolSchema parses unregister action", () => {
|
|
569
|
-
const result = ManageSecureCommandToolSchema.parse({
|
|
570
|
-
action: "unregister",
|
|
571
|
-
toolName: "aws-cli",
|
|
572
|
-
});
|
|
573
|
-
expect(result.action).toBe("unregister");
|
|
574
|
-
});
|
|
575
|
-
|
|
576
|
-
test("ApprovalRequiredSchema parses valid notification", () => {
|
|
577
|
-
const result = ApprovalRequiredSchema.parse({
|
|
578
|
-
proposal: {
|
|
579
|
-
type: "http",
|
|
580
|
-
credentialHandle: "local_static:github/api_key",
|
|
581
|
-
method: "DELETE",
|
|
582
|
-
url: "https://api.github.com/repos/test",
|
|
583
|
-
purpose: "Delete repository",
|
|
584
|
-
},
|
|
585
|
-
proposalHash: "abcdef1234567890".repeat(4),
|
|
586
|
-
renderedProposal: "Authenticated HTTP Request\n Method: DELETE",
|
|
587
|
-
sessionId: "sess-001",
|
|
588
|
-
});
|
|
589
|
-
expect(result.sessionId).toBe("sess-001");
|
|
590
|
-
});
|
|
591
|
-
|
|
592
|
-
test("RecordGrantSchema parses valid grant recording", () => {
|
|
593
|
-
const result = RecordGrantSchema.parse({
|
|
594
|
-
decision: {
|
|
595
|
-
proposal: {
|
|
596
|
-
type: "http",
|
|
597
|
-
credentialHandle: "local_static:github/api_key",
|
|
598
|
-
method: "GET",
|
|
599
|
-
url: "https://api.github.com/repos",
|
|
600
|
-
purpose: "List repos",
|
|
601
|
-
},
|
|
602
|
-
proposalHash: "abcdef1234567890".repeat(4),
|
|
603
|
-
decision: "approved",
|
|
604
|
-
decidedBy: "guardian:user",
|
|
605
|
-
decidedAt: new Date().toISOString(),
|
|
606
|
-
},
|
|
607
|
-
sessionId: "sess-001",
|
|
608
|
-
});
|
|
609
|
-
expect(result.sessionId).toBe("sess-001");
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
test("ListGrantsSchema accepts empty filter", () => {
|
|
613
|
-
const result = ListGrantsSchema.parse({});
|
|
614
|
-
expect(result.sessionId).toBeUndefined();
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
test("RevokeGrantSchema parses valid revocation", () => {
|
|
618
|
-
const result = RevokeGrantSchema.parse({
|
|
619
|
-
grantId: "grant-001",
|
|
620
|
-
reason: "Session ended",
|
|
621
|
-
});
|
|
622
|
-
expect(result.grantId).toBe("grant-001");
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
test("ListAuditRecordsSchema parses with pagination", () => {
|
|
626
|
-
const result = ListAuditRecordsSchema.parse({
|
|
627
|
-
sessionId: "sess-001",
|
|
628
|
-
limit: 50,
|
|
629
|
-
cursor: "cursor-abc",
|
|
630
|
-
});
|
|
631
|
-
expect(result.limit).toBe(50);
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
// ---------------------------------------------------------------------------
|
|
636
|
-
// Contract completeness — all methods have schemas
|
|
637
|
-
// ---------------------------------------------------------------------------
|
|
638
|
-
|
|
639
|
-
describe("CesRpcSchemas completeness", () => {
|
|
640
|
-
test("every CesRpcMethod has request and response schemas", () => {
|
|
641
|
-
for (const method of Object.values(CesRpcMethod)) {
|
|
642
|
-
const entry = CesRpcSchemas[method];
|
|
643
|
-
expect(entry).toBeDefined();
|
|
644
|
-
expect(entry.request).toBeDefined();
|
|
645
|
-
expect(entry.response).toBeDefined();
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// ---------------------------------------------------------------------------
|
|
651
|
-
// No secret-return fields guard
|
|
652
|
-
// ---------------------------------------------------------------------------
|
|
653
|
-
|
|
654
|
-
describe("no secret-return fields", () => {
|
|
655
|
-
test("PersistentGrantRecordSchema does not have secretValue or credential_value fields", () => {
|
|
656
|
-
const shape = PersistentGrantRecordSchema.shape;
|
|
657
|
-
expect("secretValue" in shape).toBe(false);
|
|
658
|
-
expect("credential_value" in shape).toBe(false);
|
|
659
|
-
expect("secret" in shape).toBe(false);
|
|
660
|
-
expect("accessToken" in shape).toBe(false);
|
|
661
|
-
expect("refreshToken" in shape).toBe(false);
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
test("AuditRecordSummarySchema does not have secretValue fields", () => {
|
|
665
|
-
const shape = AuditRecordSummarySchema.shape;
|
|
666
|
-
expect("secretValue" in shape).toBe(false);
|
|
667
|
-
expect("credential_value" in shape).toBe(false);
|
|
668
|
-
expect("secret" in shape).toBe(false);
|
|
669
|
-
expect("accessToken" in shape).toBe(false);
|
|
670
|
-
expect("refreshToken" in shape).toBe(false);
|
|
671
|
-
});
|
|
672
|
-
|
|
673
|
-
test("MakeAuthenticatedRequestResponseSchema does not return secrets", () => {
|
|
674
|
-
const shape = MakeAuthenticatedRequestResponseSchema.shape;
|
|
675
|
-
expect("secretValue" in shape).toBe(false);
|
|
676
|
-
expect("credential_value" in shape).toBe(false);
|
|
677
|
-
expect("accessToken" in shape).toBe(false);
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
test("RunAuthenticatedCommandResponseSchema does not return secrets", () => {
|
|
681
|
-
const shape = RunAuthenticatedCommandResponseSchema.shape;
|
|
682
|
-
expect("secretValue" in shape).toBe(false);
|
|
683
|
-
expect("credential_value" in shape).toBe(false);
|
|
684
|
-
expect("accessToken" in shape).toBe(false);
|
|
685
|
-
});
|
|
686
|
-
});
|