@r4-sdk/cli 1.0.0 → 1.0.2
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/README.md +21 -4
- package/lib/index.js +332 -1883
- package/lib/index.js.map +1 -1
- package/package.json +10 -10
package/lib/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command45 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/agent/create.ts
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
|
|
10
10
|
// src/lib/client.ts
|
|
11
|
+
import os from "os";
|
|
12
|
+
var AGENT_RUNTIME_HOSTNAME_HEADER = "X-R4-Agent-Hostname";
|
|
11
13
|
var CliClient = class {
|
|
12
14
|
apiKey;
|
|
13
15
|
baseUrl;
|
|
@@ -15,19 +17,19 @@ var CliClient = class {
|
|
|
15
17
|
this.apiKey = apiKey;
|
|
16
18
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
17
19
|
}
|
|
18
|
-
normalizeMachinePath(
|
|
19
|
-
if (!
|
|
20
|
+
normalizeMachinePath(path6) {
|
|
21
|
+
if (!path6 || /^\w+:\/\//.test(path6)) {
|
|
20
22
|
throw new Error("Machine request path must be a relative machine API path.");
|
|
21
23
|
}
|
|
22
|
-
const normalizedPath =
|
|
24
|
+
const normalizedPath = path6.startsWith("/") ? path6 : `/${path6}`;
|
|
23
25
|
return normalizedPath.startsWith("/api/v1/machine") ? normalizedPath : `/api/v1/machine${normalizedPath}`;
|
|
24
26
|
}
|
|
25
27
|
/**
|
|
26
28
|
* Make an authenticated request to the machine API.
|
|
27
29
|
* Handles error responses consistently with the SDK pattern.
|
|
28
30
|
*/
|
|
29
|
-
async request(method,
|
|
30
|
-
const url = `${this.baseUrl}${
|
|
31
|
+
async request(method, path6, body) {
|
|
32
|
+
const url = `${this.baseUrl}${path6}`;
|
|
31
33
|
const response = await fetch(url, {
|
|
32
34
|
method,
|
|
33
35
|
headers: {
|
|
@@ -62,11 +64,23 @@ var CliClient = class {
|
|
|
62
64
|
}
|
|
63
65
|
/** Register or re-confirm the local agent runtime public key. */
|
|
64
66
|
async registerAgentPublicKey(body) {
|
|
65
|
-
|
|
66
|
-
"POST",
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
const response = await fetch(`${this.baseUrl}/api/v1/machine/vault/public-key`, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: {
|
|
70
|
+
"X-API-Key": this.apiKey,
|
|
71
|
+
"Content-Type": "application/json",
|
|
72
|
+
[AGENT_RUNTIME_HOSTNAME_HEADER]: os.hostname()
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify(body)
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
78
|
+
const error = errorBody?.error;
|
|
79
|
+
const errorMessage = error?.message || `HTTP ${response.status}: ${response.statusText}`;
|
|
80
|
+
const errorCode = typeof error?.code === "string" ? ` [${error.code}]` : "";
|
|
81
|
+
throw new Error(`R4 API Error${errorCode}: ${errorMessage}`);
|
|
82
|
+
}
|
|
83
|
+
return response.json();
|
|
70
84
|
}
|
|
71
85
|
/** Read the authenticated machine principal and resolved policy context. */
|
|
72
86
|
async getMachineIdentity() {
|
|
@@ -95,6 +109,27 @@ var CliClient = class {
|
|
|
95
109
|
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items`
|
|
96
110
|
);
|
|
97
111
|
}
|
|
112
|
+
/** List directly shared vault items whose parent vault is otherwise hidden. */
|
|
113
|
+
async getSharedVaultItems() {
|
|
114
|
+
return this.request(
|
|
115
|
+
"GET",
|
|
116
|
+
"/api/v1/machine/vault/shared-items"
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
/** Retrieve the full machine vault-item detail payload. */
|
|
120
|
+
async getVaultItemDetail(vaultId, itemId) {
|
|
121
|
+
return this.request(
|
|
122
|
+
"GET",
|
|
123
|
+
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items/${encodeURIComponent(itemId)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
/** Retrieve a zero-trust download ticket for a vault attachment. */
|
|
127
|
+
async getVaultAttachmentDownloadTicket(vaultId, assetId) {
|
|
128
|
+
return this.request(
|
|
129
|
+
"GET",
|
|
130
|
+
`/api/v1/machine/attachments/${encodeURIComponent(vaultId)}/assets/${encodeURIComponent(assetId)}/download-ticket`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
98
133
|
/** Create a checkpoint-signed vault item from a prebuilt JSON payload. */
|
|
99
134
|
async createVaultItem(vaultId, body) {
|
|
100
135
|
return this.request(
|
|
@@ -303,18 +338,18 @@ function withErrorHandler(fn) {
|
|
|
303
338
|
}
|
|
304
339
|
|
|
305
340
|
// src/lib/config.ts
|
|
306
|
-
import fs2 from "
|
|
341
|
+
import fs2 from "fs";
|
|
307
342
|
|
|
308
343
|
// src/lib/profile-paths.ts
|
|
309
|
-
import fs from "
|
|
310
|
-
import
|
|
311
|
-
import path from "
|
|
344
|
+
import fs from "fs";
|
|
345
|
+
import os2 from "os";
|
|
346
|
+
import path from "path";
|
|
312
347
|
function sanitizeProfileName(profileName) {
|
|
313
348
|
const sanitized = profileName.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
314
349
|
return sanitized || "default";
|
|
315
350
|
}
|
|
316
351
|
function getR4HomeDir() {
|
|
317
|
-
return path.join(
|
|
352
|
+
return path.join(os2.homedir(), ".r4");
|
|
318
353
|
}
|
|
319
354
|
function getProfilesDir() {
|
|
320
355
|
return path.join(getR4HomeDir(), "profiles");
|
|
@@ -539,7 +574,7 @@ function clearManagedProfileDirectory(profileName) {
|
|
|
539
574
|
}
|
|
540
575
|
|
|
541
576
|
// src/lib/credentials-store.ts
|
|
542
|
-
import fs3 from "
|
|
577
|
+
import fs3 from "fs";
|
|
543
578
|
function buildApiKeyFromParts(accessKey, secretKey) {
|
|
544
579
|
if (!accessKey || !secretKey) {
|
|
545
580
|
return void 0;
|
|
@@ -604,9 +639,9 @@ function clearProfileCredentials(profileName) {
|
|
|
604
639
|
}
|
|
605
640
|
|
|
606
641
|
// src/lib/private-key.ts
|
|
607
|
-
import crypto from "
|
|
608
|
-
import fs4 from "
|
|
609
|
-
import path2 from "
|
|
642
|
+
import crypto from "crypto";
|
|
643
|
+
import fs4 from "fs";
|
|
644
|
+
import path2 from "path";
|
|
610
645
|
function getDefaultPrivateKeyPath(profileName) {
|
|
611
646
|
return getManagedPrivateKeyPath(profileName);
|
|
612
647
|
}
|
|
@@ -872,1793 +907,135 @@ function buildCreateAgentBody(options) {
|
|
|
872
907
|
};
|
|
873
908
|
}
|
|
874
909
|
function createCommand() {
|
|
875
|
-
return new Command("create").description("Create a new agent").requiredOption("--name <name>", "Agent name").requiredOption("--domain-tenant-id <id>", "Target domain tenant ID").option("--budget-id <id>", "Existing budget ID to assign").option("--vault-id <id>", "Optional vault ID to store credentials in").option("--new-budget-name <name>", "Create and assign a new per-agent budget").option("--new-budget-description <text>", "Description for the new per-agent budget").option("--new-budget-target <amount>", "Target amount for the new per-agent budget").option(
|
|
876
|
-
"--security-group-id <id>",
|
|
877
|
-
"Security group IDs (repeat or use comma-separated values)",
|
|
878
|
-
collectOptionValues,
|
|
879
|
-
[]
|
|
880
|
-
).option(
|
|
881
|
-
"--permission <grant>",
|
|
882
|
-
"Machine permission grant (repeat or use comma-separated values)",
|
|
883
|
-
collectOptionValues,
|
|
884
|
-
[]
|
|
885
|
-
).action(
|
|
886
|
-
withErrorHandler(
|
|
887
|
-
async (options, cmd) => {
|
|
888
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
889
|
-
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
890
|
-
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
891
|
-
const body = buildCreateAgentBody(options);
|
|
892
|
-
const spinner = ora("Creating agent...").start();
|
|
893
|
-
const response = await client.createAgent(body);
|
|
894
|
-
spinner.stop();
|
|
895
|
-
if (globalOpts.json) {
|
|
896
|
-
console.log(JSON.stringify(response, null, 2));
|
|
897
|
-
return;
|
|
898
|
-
}
|
|
899
|
-
success(`Created agent "${response.name}"`);
|
|
900
|
-
printDetail(
|
|
901
|
-
[
|
|
902
|
-
["ID", response.id],
|
|
903
|
-
["Access Key", response.accessKey],
|
|
904
|
-
["Access Secret", response.accessSecret],
|
|
905
|
-
["Vault Item", response.vaultItemId]
|
|
906
|
-
],
|
|
907
|
-
false
|
|
908
|
-
);
|
|
909
|
-
}
|
|
910
|
-
)
|
|
911
|
-
);
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// src/commands/agent/get.ts
|
|
915
|
-
import { Command as Command2 } from "commander";
|
|
916
|
-
import chalk2 from "chalk";
|
|
917
|
-
import ora2 from "ora";
|
|
918
|
-
function getCommand() {
|
|
919
|
-
return new Command2("get").description("Get agent details").argument("<id>", "Agent ID").action(
|
|
920
|
-
withErrorHandler(async (id, _opts, cmd) => {
|
|
921
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
922
|
-
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
923
|
-
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
924
|
-
const spinner = ora2("Fetching agent...").start();
|
|
925
|
-
const agent = await client.getAgent(id);
|
|
926
|
-
spinner.stop();
|
|
927
|
-
if (globalOpts.json) {
|
|
928
|
-
console.log(JSON.stringify(agent, null, 2));
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
console.log();
|
|
932
|
-
console.log(chalk2.bold(` ${agent.name}`));
|
|
933
|
-
console.log();
|
|
934
|
-
printDetail(
|
|
935
|
-
[
|
|
936
|
-
["ID", agent.id],
|
|
937
|
-
["Tenant", agent.tenantName],
|
|
938
|
-
["Domain", agent.domainName],
|
|
939
|
-
["Domain Tenant ID", agent.domainTenantId],
|
|
940
|
-
["Budget", agent.budgetId],
|
|
941
|
-
["Public Key Ready", agent.isPublicKeyRegistered ? "Yes" : "No"],
|
|
942
|
-
["Created At", agent.createdAt],
|
|
943
|
-
["Updated At", agent.updatedAt],
|
|
944
|
-
["Archived At", agent.archivedAt]
|
|
945
|
-
],
|
|
946
|
-
false
|
|
947
|
-
);
|
|
948
|
-
if (agent.securityGroups.length > 0) {
|
|
949
|
-
console.log();
|
|
950
|
-
console.log(chalk2.bold(" Security Groups"));
|
|
951
|
-
console.log();
|
|
952
|
-
printTable(
|
|
953
|
-
["ID", "Name"],
|
|
954
|
-
agent.securityGroups.map((group) => [group.id, group.name]),
|
|
955
|
-
false
|
|
956
|
-
);
|
|
957
|
-
}
|
|
958
|
-
console.log();
|
|
959
|
-
})
|
|
960
|
-
);
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// src/commands/agent/get-tenant-roles.ts
|
|
964
|
-
import { Command as Command3 } from "commander";
|
|
965
|
-
import ora3 from "ora";
|
|
966
|
-
function getTenantRolesCommand() {
|
|
967
|
-
return new Command3("get-tenant-roles").description("Get the tenant roles assigned to an agent").argument("<id>", "Agent ID").action(
|
|
968
|
-
withErrorHandler(async (id, _opts, cmd) => {
|
|
969
|
-
const globalOpts = cmd.optsWithGlobals();
|
|
970
|
-
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
971
|
-
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
972
|
-
const spinner = ora3("Fetching agent tenant roles...").start();
|
|
973
|
-
const response = await client.getAgentTenantRoles(id);
|
|
974
|
-
spinner.stop();
|
|
975
|
-
if (globalOpts.json) {
|
|
976
|
-
console.log(JSON.stringify(response, null, 2));
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
console.log();
|
|
980
|
-
printDetail(
|
|
981
|
-
[
|
|
982
|
-
["Agent ID", id],
|
|
983
|
-
["Explicit Roles", response.roles.join(", ") || "(none)"],
|
|
984
|
-
["Inherited Roles", response.inheritedRoles.join(", ") || "(none)"]
|
|
985
|
-
],
|
|
986
|
-
false
|
|
987
|
-
);
|
|
988
|
-
console.log();
|
|
989
|
-
})
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
// src/commands/agent/init.ts
|
|
994
|
-
import { Command as Command5 } from "commander";
|
|
995
|
-
import ora5 from "ora";
|
|
996
|
-
|
|
997
|
-
// src/commands/doctor.ts
|
|
998
|
-
import { Command as Command4 } from "commander";
|
|
999
|
-
import chalk3 from "chalk";
|
|
1000
|
-
import ora4 from "ora";
|
|
1001
|
-
|
|
1002
|
-
// ../node/lib/index.js
|
|
1003
|
-
import { randomUUID } from "node:crypto";
|
|
1004
|
-
import path3 from "node:path";
|
|
1005
|
-
import crypto2 from "node:crypto";
|
|
1006
|
-
import fs5 from "node:fs";
|
|
1007
|
-
import path4 from "node:path";
|
|
1008
|
-
import fs22 from "node:fs";
|
|
1009
|
-
import path22 from "node:path";
|
|
1010
|
-
var R4Client = class {
|
|
1011
|
-
apiKey;
|
|
1012
|
-
baseUrl;
|
|
1013
|
-
constructor(apiKey, baseUrl) {
|
|
1014
|
-
this.apiKey = apiKey;
|
|
1015
|
-
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
1016
|
-
}
|
|
1017
|
-
buildHeaders() {
|
|
1018
|
-
return {
|
|
1019
|
-
"X-API-Key": this.apiKey,
|
|
1020
|
-
"Content-Type": "application/json"
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
normalizeMachinePath(path42) {
|
|
1024
|
-
if (!path42 || /^\w+:\/\//.test(path42)) {
|
|
1025
|
-
throw new Error("Machine request path must be a relative machine API path.");
|
|
1026
|
-
}
|
|
1027
|
-
const normalizedPath = path42.startsWith("/") ? path42 : `/${path42}`;
|
|
1028
|
-
return normalizedPath.startsWith("/api/v1/machine") ? normalizedPath : `/api/v1/machine${normalizedPath}`;
|
|
1029
|
-
}
|
|
1030
|
-
async request(path42, init) {
|
|
1031
|
-
const response = await fetch(`${this.baseUrl}${path42}`, {
|
|
1032
|
-
...init,
|
|
1033
|
-
headers: {
|
|
1034
|
-
...this.buildHeaders(),
|
|
1035
|
-
...init.headers ?? {}
|
|
1036
|
-
}
|
|
1037
|
-
});
|
|
1038
|
-
if (!response.ok) {
|
|
1039
|
-
const errorBody = await response.json().catch(() => ({}));
|
|
1040
|
-
const errorMessage = typeof errorBody.error?.message === "string" ? errorBody.error.message : `HTTP ${response.status}: ${response.statusText}`;
|
|
1041
|
-
const errorCode = typeof errorBody.error?.code === "string" ? ` [${errorBody.error.code}]` : "";
|
|
1042
|
-
throw new Error(`R4 API Error${errorCode}: ${errorMessage}`);
|
|
1043
|
-
}
|
|
1044
|
-
if (response.status === 204) {
|
|
1045
|
-
return void 0;
|
|
1046
|
-
}
|
|
1047
|
-
const responseText = await response.text();
|
|
1048
|
-
if (!responseText) {
|
|
1049
|
-
return void 0;
|
|
1050
|
-
}
|
|
1051
|
-
try {
|
|
1052
|
-
return JSON.parse(responseText);
|
|
1053
|
-
} catch {
|
|
1054
|
-
return responseText;
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
/**
|
|
1058
|
-
* Calls any existing machine API route through the authenticated client.
|
|
1059
|
-
* Accepts either `/api/v1/machine/...` or the shorter `/...` machine path.
|
|
1060
|
-
*/
|
|
1061
|
-
async requestMachine(params) {
|
|
1062
|
-
return this.request(this.normalizeMachinePath(params.path), {
|
|
1063
|
-
method: params.method,
|
|
1064
|
-
body: params.body === void 0 ? void 0 : JSON.stringify(params.body)
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
/**
|
|
1068
|
-
* Registers or re-confirms the agent runtime's local RSA public key.
|
|
1069
|
-
*/
|
|
1070
|
-
async registerAgentPublicKey(body) {
|
|
1071
|
-
return this.request("/api/v1/machine/vault/public-key", {
|
|
1072
|
-
method: "POST",
|
|
1073
|
-
body: JSON.stringify(body)
|
|
1074
|
-
});
|
|
1075
|
-
}
|
|
1076
|
-
/**
|
|
1077
|
-
* Returns the current machine principal, session context, and resolved policy.
|
|
1078
|
-
*/
|
|
1079
|
-
async getMachineIdentity() {
|
|
1080
|
-
return this.request("/api/v1/machine/me", {
|
|
1081
|
-
method: "GET"
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
/**
|
|
1085
|
-
* Lists all accessible non-hidden vaults. When `projectId` is provided, the
|
|
1086
|
-
* backend additionally filters to vaults associated with that project.
|
|
1087
|
-
*/
|
|
1088
|
-
async listVaults(projectId) {
|
|
1089
|
-
const search = projectId ? `?projectId=${encodeURIComponent(projectId)}` : "";
|
|
1090
|
-
return this.request(`/api/v1/machine/vault${search}`, {
|
|
1091
|
-
method: "GET"
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Retrieves the active wrapped DEK for the authenticated agent on a vault.
|
|
1096
|
-
*/
|
|
1097
|
-
async getAgentWrappedKey(vaultId) {
|
|
1098
|
-
return this.request(
|
|
1099
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/wrapped-key`,
|
|
1100
|
-
{ method: "GET" }
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Retrieves the trusted user-key directory for a vault so the runtime can
|
|
1105
|
-
* verify wrapped-DEK signatures locally.
|
|
1106
|
-
*/
|
|
1107
|
-
async getVaultUserKeyDirectory(vaultId, params) {
|
|
1108
|
-
const searchParams = new URLSearchParams();
|
|
1109
|
-
if (params?.knownTransparencyVersion !== void 0) {
|
|
1110
|
-
searchParams.set("knownTransparencyVersion", String(params.knownTransparencyVersion));
|
|
1111
|
-
}
|
|
1112
|
-
if (params?.knownTransparencyHash) {
|
|
1113
|
-
searchParams.set("knownTransparencyHash", params.knownTransparencyHash);
|
|
1114
|
-
}
|
|
1115
|
-
const search = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
|
|
1116
|
-
return this.request(
|
|
1117
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/public-keys${search}`,
|
|
1118
|
-
{ method: "GET" }
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Lists all items in a vault with lightweight metadata.
|
|
1123
|
-
*/
|
|
1124
|
-
async listVaultItems(vaultId) {
|
|
1125
|
-
return this.request(
|
|
1126
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items`,
|
|
1127
|
-
{ method: "GET" }
|
|
1128
|
-
);
|
|
1129
|
-
}
|
|
1130
|
-
/**
|
|
1131
|
-
* Retrieves the full field payloads for a vault item.
|
|
1132
|
-
*/
|
|
1133
|
-
async getVaultItemDetail(vaultId, itemId) {
|
|
1134
|
-
return this.request(
|
|
1135
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items/${encodeURIComponent(itemId)}`,
|
|
1136
|
-
{ method: "GET" }
|
|
1137
|
-
);
|
|
1138
|
-
}
|
|
1139
|
-
/**
|
|
1140
|
-
* Retrieves the provider-scoped runtime bundle needed for local decryption.
|
|
1141
|
-
*/
|
|
1142
|
-
async getTokenProviderRuntime(tokenProviderId, params) {
|
|
1143
|
-
const searchParams = new URLSearchParams();
|
|
1144
|
-
if (params?.knownTransparencyVersion !== void 0) {
|
|
1145
|
-
searchParams.set("knownTransparencyVersion", String(params.knownTransparencyVersion));
|
|
1146
|
-
}
|
|
1147
|
-
if (params?.knownTransparencyHash) {
|
|
1148
|
-
searchParams.set("knownTransparencyHash", params.knownTransparencyHash);
|
|
1149
|
-
}
|
|
1150
|
-
const search = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
|
|
1151
|
-
return this.request(
|
|
1152
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/runtime${search}`,
|
|
1153
|
-
{ method: "GET" }
|
|
1154
|
-
);
|
|
1155
|
-
}
|
|
1156
|
-
/**
|
|
1157
|
-
* Runs the managed budget gate before a token-provider call is sent to the vendor.
|
|
1158
|
-
*/
|
|
1159
|
-
async authorizeTokenProviderUsage(tokenProviderId, body) {
|
|
1160
|
-
return this.request(
|
|
1161
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/authorize-usage`,
|
|
1162
|
-
{
|
|
1163
|
-
method: "POST",
|
|
1164
|
-
body: JSON.stringify(body)
|
|
1165
|
-
}
|
|
1166
|
-
);
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Publishes finalized token usage for a completed managed provider call.
|
|
1170
|
-
*/
|
|
1171
|
-
async reportTokenProviderUsage(tokenProviderId, body) {
|
|
1172
|
-
return this.request(
|
|
1173
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/report-usage`,
|
|
1174
|
-
{
|
|
1175
|
-
method: "POST",
|
|
1176
|
-
body: JSON.stringify(body)
|
|
1177
|
-
}
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
};
|
|
1181
|
-
var TRANSPARENCY_WITNESS_PAYLOAD_PREFIX = "r4-transparency-witness-v1";
|
|
1182
|
-
var DEFAULT_TRANSPARENCY_WITNESS_URL = "https://transparency.r4.dev";
|
|
1183
|
-
var TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1184
|
-
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JhILFiS/BOWR9laubW
|
|
1185
|
-
g2vepQy26BXAlnrscZZVQUzBBaCM4hWobpt3Nh77vxP0gqVAJXP1hVhPPwxGQnOF
|
|
1186
|
-
4Qg/RK4iEETjMdmh3KMqFX9MeE9tP4cTOGtsgWsedNpu6TvMT+2vu+0ltmr7p4Xv
|
|
1187
|
-
H0ID48Q8JLeNksc/RekrsfzQ9DVtXFS7z1FF2VQgzamdJsW9hGMiM7Q+0iXei7PW
|
|
1188
|
-
3PsLd1aNtqJ3lIj3t12qFiJiYyKF0hEq0//Abgb9SgDv/WOlRG1Ianf1/fnP2jer
|
|
1189
|
-
ZYiZSylXqQdun0Db2d0+FDm/znV2AGAmBEXm6qnCogEHu77LoLyCyJOlB9WNtRwh
|
|
1190
|
-
KnbzTmE2Mw/43jxvCcR7pE5kik/tdeMvqGFZfg3ozUG9eM0q0TURH6g9b9J4sBnR
|
|
1191
|
-
dxz2PbF4cl/AeL4ANPmLz3kUQaDA6wR0veVk5jV+Uqr55TYz/zEbY1rtJbmnc53Q
|
|
1192
|
-
ihPS6xtSiexrqnOgqm/AVbiRhxjPqfg3/VJM3zR5Blnu02AqVR9kCT0WkyEWRz5X
|
|
1193
|
-
6HU8DEocJIPz8UwBMKQ7rnjMPv/Fjpuav/EIad5vOdfxCZkjyTYoQg8vLUyfXvgD
|
|
1194
|
-
mBWFgKIN8GTRyM+LjZIgznjN58dZ8ZvsGd14oKnH7WgAh9FVh8ri7gNmsdJeRTn/
|
|
1195
|
-
2zDkTlx+FQxAxqFaYV7qCvcCAwEAAQ==
|
|
1196
|
-
-----END PUBLIC KEY-----`;
|
|
1197
|
-
var buildOrgUserKeyDirectoryWitnessPayload = (orgId, head) => [
|
|
1198
|
-
TRANSPARENCY_WITNESS_PAYLOAD_PREFIX,
|
|
1199
|
-
"org-user-key-directory",
|
|
1200
|
-
orgId,
|
|
1201
|
-
String(head.version),
|
|
1202
|
-
head.hash
|
|
1203
|
-
].join(":");
|
|
1204
|
-
var buildAgentPublicKeyWitnessPayload = (agentId, head) => [
|
|
1205
|
-
TRANSPARENCY_WITNESS_PAYLOAD_PREFIX,
|
|
1206
|
-
"agent-public-key",
|
|
1207
|
-
agentId,
|
|
1208
|
-
String(head.version),
|
|
1209
|
-
head.hash
|
|
1210
|
-
].join(":");
|
|
1211
|
-
var buildOrgUserKeyDirectoryWitnessPath = (orgId) => `v1/orgs/${orgId}/user-key-directory-head.json`;
|
|
1212
|
-
var buildAgentPublicKeyWitnessPath = (agentId) => `v1/agents/${agentId}/public-key-head.json`;
|
|
1213
|
-
var buildTransparencyWitnessUrl = (baseUrl, path42) => `${baseUrl.replace(/\/+$/, "")}/${path42.replace(/^\/+/, "")}`;
|
|
1214
|
-
function parseHostname(apiBaseUrl) {
|
|
1215
|
-
const trimmed = apiBaseUrl.trim();
|
|
1216
|
-
if (!trimmed) {
|
|
1217
|
-
return null;
|
|
1218
|
-
}
|
|
1219
|
-
try {
|
|
1220
|
-
const normalized = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
|
|
1221
|
-
return new URL(normalized).hostname.toLowerCase();
|
|
1222
|
-
} catch {
|
|
1223
|
-
return null;
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
var shouldUseDefaultTransparencyWitness = (apiBaseUrl) => {
|
|
1227
|
-
const hostname = parseHostname(apiBaseUrl);
|
|
1228
|
-
return hostname === "r4.dev" || hostname === "api.r4.dev";
|
|
1229
|
-
};
|
|
1230
|
-
var resolveTransparencyWitnessBaseUrl = (params) => {
|
|
1231
|
-
const configuredBaseUrl = params.configuredBaseUrl?.trim();
|
|
1232
|
-
if (configuredBaseUrl) {
|
|
1233
|
-
return configuredBaseUrl;
|
|
1234
|
-
}
|
|
1235
|
-
return shouldUseDefaultTransparencyWitness(params.apiBaseUrl) ? DEFAULT_TRANSPARENCY_WITNESS_URL : null;
|
|
1236
|
-
};
|
|
1237
|
-
var RSA_OAEP_CONFIG = {
|
|
1238
|
-
padding: crypto2.constants.RSA_PKCS1_OAEP_PADDING,
|
|
1239
|
-
oaepHash: "sha256"
|
|
1240
|
-
};
|
|
1241
|
-
var RSA_PSS_SIGN_CONFIG = {
|
|
1242
|
-
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
1243
|
-
saltLength: 32
|
|
1244
|
-
};
|
|
1245
|
-
var USER_KEY_ROTATION_PREFIX = "r4-user-key-rotation-v1";
|
|
1246
|
-
var USER_KEY_DIRECTORY_CHECKPOINT_PREFIX = "r4-user-key-directory-checkpoint-v1";
|
|
1247
|
-
var USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX = "r4-user-key-directory-transparency-entry-v1";
|
|
1248
|
-
var AGENT_PUBLIC_KEY_TRANSPARENCY_ENTRY_PREFIX = "r4-agent-public-key-transparency-entry-v1";
|
|
1249
|
-
var WRAPPED_DEK_SIGNATURE_PREFIX = "r4-wrapped-dek-signature-v1";
|
|
1250
|
-
var VAULT_SUMMARY_CHECKPOINT_PREFIX = "r4-vault-summary-checkpoint-v1";
|
|
1251
|
-
var VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX = "r4-vault-item-detail-checkpoint-v1";
|
|
1252
|
-
function pemToDer(pem, beginLabel, endLabel) {
|
|
1253
|
-
const derBase64 = pem.replace(beginLabel, "").replace(endLabel, "").replace(/\s/g, "");
|
|
1254
|
-
return Buffer.from(derBase64, "base64");
|
|
1255
|
-
}
|
|
1256
|
-
function getWrappedDekFingerprint(wrappedDek) {
|
|
1257
|
-
return crypto2.createHash("sha256").update(Buffer.from(wrappedDek, "base64")).digest("hex");
|
|
1258
|
-
}
|
|
1259
|
-
function getCheckpointFingerprint(prefix, canonicalJson) {
|
|
1260
|
-
return `${prefix}:${crypto2.createHash("sha256").update(canonicalJson, "utf8").digest("hex")}`;
|
|
1261
|
-
}
|
|
1262
|
-
function loadPrivateKey2(privateKeyPath) {
|
|
1263
|
-
return fs5.readFileSync(path4.resolve(privateKeyPath), "utf8").trim();
|
|
1264
|
-
}
|
|
1265
|
-
function derivePublicKey2(privateKeyPem) {
|
|
1266
|
-
return crypto2.createPublicKey(privateKeyPem).export({
|
|
1267
|
-
type: "spki",
|
|
1268
|
-
format: "pem"
|
|
1269
|
-
}).toString();
|
|
1270
|
-
}
|
|
1271
|
-
function getPublicKeyFingerprint(publicKeyPem) {
|
|
1272
|
-
const derBytes = pemToDer(publicKeyPem, "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----");
|
|
1273
|
-
return crypto2.createHash("sha256").update(derBytes).digest("hex");
|
|
1274
|
-
}
|
|
1275
|
-
function buildUserKeyRotationPayload(previousUserKeyPairId, newPublicKeyFingerprint) {
|
|
1276
|
-
return `${USER_KEY_ROTATION_PREFIX}:${previousUserKeyPairId}:${newPublicKeyFingerprint}`;
|
|
1277
|
-
}
|
|
1278
|
-
function verifyUserKeyRotation(previousUserKeyPairId, newPublicKeyPem, rotationSignature, previousPublicKeyPem) {
|
|
1279
|
-
const payload = buildUserKeyRotationPayload(
|
|
1280
|
-
previousUserKeyPairId,
|
|
1281
|
-
getPublicKeyFingerprint(newPublicKeyPem)
|
|
1282
|
-
);
|
|
1283
|
-
try {
|
|
1284
|
-
return crypto2.verify(
|
|
1285
|
-
"sha256",
|
|
1286
|
-
Buffer.from(payload, "utf8"),
|
|
1287
|
-
{
|
|
1288
|
-
key: previousPublicKeyPem,
|
|
1289
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1290
|
-
},
|
|
1291
|
-
Buffer.from(rotationSignature, "base64")
|
|
1292
|
-
);
|
|
1293
|
-
} catch {
|
|
1294
|
-
return false;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
function verifyTransparencyWitnessPayload(payload, signature, publicKeyPem) {
|
|
1298
|
-
try {
|
|
1299
|
-
return crypto2.verify(
|
|
1300
|
-
"sha256",
|
|
1301
|
-
Buffer.from(payload, "utf8"),
|
|
1302
|
-
{
|
|
1303
|
-
key: publicKeyPem,
|
|
1304
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1305
|
-
},
|
|
1306
|
-
Buffer.from(signature, "base64")
|
|
1307
|
-
);
|
|
1308
|
-
} catch {
|
|
1309
|
-
return false;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
function verifyOrgUserKeyDirectoryWitnessArtifact(artifact, publicKeyPem) {
|
|
1313
|
-
return verifyTransparencyWitnessPayload(
|
|
1314
|
-
buildOrgUserKeyDirectoryWitnessPayload(artifact.orgId, artifact.head),
|
|
1315
|
-
artifact.signature,
|
|
1316
|
-
publicKeyPem
|
|
1317
|
-
);
|
|
1318
|
-
}
|
|
1319
|
-
function verifyAgentPublicKeyWitnessArtifact(artifact, publicKeyPem) {
|
|
1320
|
-
return verifyTransparencyWitnessPayload(
|
|
1321
|
-
buildAgentPublicKeyWitnessPayload(artifact.agentId, artifact.head),
|
|
1322
|
-
artifact.signature,
|
|
1323
|
-
publicKeyPem
|
|
1324
|
-
);
|
|
1325
|
-
}
|
|
1326
|
-
function buildAgentPublicKeyTransparencyEntryHash(entry) {
|
|
1327
|
-
return getCheckpointFingerprint(
|
|
1328
|
-
AGENT_PUBLIC_KEY_TRANSPARENCY_ENTRY_PREFIX,
|
|
1329
|
-
JSON.stringify({
|
|
1330
|
-
agentId: entry.agentId,
|
|
1331
|
-
version: entry.version,
|
|
1332
|
-
encryptionKeyId: entry.encryptionKeyId,
|
|
1333
|
-
publicKey: entry.publicKey,
|
|
1334
|
-
fingerprint: entry.fingerprint,
|
|
1335
|
-
previousEncryptionKeyId: entry.previousEncryptionKeyId ?? null,
|
|
1336
|
-
rotationSignature: entry.rotationSignature ?? null,
|
|
1337
|
-
previousEntryHash: entry.previousEntryHash ?? null
|
|
1338
|
-
})
|
|
1339
|
-
);
|
|
1340
|
-
}
|
|
1341
|
-
function buildAgentPublicKeyTransparencyEntry(params) {
|
|
1342
|
-
const normalized = {
|
|
1343
|
-
agentId: params.agentId,
|
|
1344
|
-
version: params.version,
|
|
1345
|
-
encryptionKeyId: params.encryptionKeyId,
|
|
1346
|
-
publicKey: params.publicKey,
|
|
1347
|
-
fingerprint: params.fingerprint ?? getPublicKeyFingerprint(params.publicKey),
|
|
1348
|
-
previousEncryptionKeyId: params.previousEncryptionKeyId ?? null,
|
|
1349
|
-
rotationSignature: params.rotationSignature ?? null,
|
|
1350
|
-
previousEntryHash: params.previousEntryHash ?? null
|
|
1351
|
-
};
|
|
1352
|
-
return {
|
|
1353
|
-
...normalized,
|
|
1354
|
-
entryHash: buildAgentPublicKeyTransparencyEntryHash(normalized)
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
function verifyAgentPublicKeyTransparencyChain(entries) {
|
|
1358
|
-
let previousVersion = null;
|
|
1359
|
-
let previousHash = null;
|
|
1360
|
-
for (const entry of entries) {
|
|
1361
|
-
if (buildAgentPublicKeyTransparencyEntryHash(entry) !== entry.entryHash) {
|
|
1362
|
-
return false;
|
|
1363
|
-
}
|
|
1364
|
-
if (entry.previousEntryHash !== previousHash) {
|
|
1365
|
-
return false;
|
|
1366
|
-
}
|
|
1367
|
-
if (previousVersion !== null && entry.version !== previousVersion + 1) {
|
|
1368
|
-
return false;
|
|
1369
|
-
}
|
|
1370
|
-
previousVersion = entry.version;
|
|
1371
|
-
previousHash = entry.entryHash;
|
|
1372
|
-
}
|
|
1373
|
-
return true;
|
|
1374
|
-
}
|
|
1375
|
-
function buildAgentPublicKeyTransparencyHead(entries) {
|
|
1376
|
-
const lastEntry = entries[entries.length - 1];
|
|
1377
|
-
if (!lastEntry) {
|
|
1378
|
-
return null;
|
|
1379
|
-
}
|
|
1380
|
-
return {
|
|
1381
|
-
version: lastEntry.version,
|
|
1382
|
-
hash: lastEntry.entryHash
|
|
1383
|
-
};
|
|
1384
|
-
}
|
|
1385
|
-
function verifyAgentPublicKeyTransparencyProof(params) {
|
|
1386
|
-
const currentEntryHead = {
|
|
1387
|
-
version: params.currentEntry.version,
|
|
1388
|
-
hash: params.currentEntry.entryHash
|
|
1389
|
-
};
|
|
1390
|
-
if (params.proof.head.version !== currentEntryHead.version || params.proof.head.hash !== currentEntryHead.hash) {
|
|
1391
|
-
return false;
|
|
1392
|
-
}
|
|
1393
|
-
if (!verifyAgentPublicKeyTransparencyChain(params.proof.entries)) {
|
|
1394
|
-
return false;
|
|
1395
|
-
}
|
|
1396
|
-
const proofHead = buildAgentPublicKeyTransparencyHead(params.proof.entries);
|
|
1397
|
-
if (!proofHead || proofHead.version !== currentEntryHead.version || proofHead.hash !== currentEntryHead.hash) {
|
|
1398
|
-
return false;
|
|
1399
|
-
}
|
|
1400
|
-
if (!params.previousHead) {
|
|
1401
|
-
return params.proof.entries.length > 0;
|
|
1402
|
-
}
|
|
1403
|
-
if (params.currentEntry.version < params.previousHead.version) {
|
|
1404
|
-
return false;
|
|
1405
|
-
}
|
|
1406
|
-
const previousEntry = params.proof.entries.find((entry) => entry.version === params.previousHead.version);
|
|
1407
|
-
if (params.currentEntry.version === params.previousHead.version) {
|
|
1408
|
-
return previousEntry?.entryHash === params.previousHead.hash;
|
|
1409
|
-
}
|
|
1410
|
-
return previousEntry?.entryHash === params.previousHead.hash;
|
|
1411
|
-
}
|
|
1412
|
-
function normalizeUserKeyDirectoryCheckpoint(checkpoint) {
|
|
1413
|
-
return {
|
|
1414
|
-
orgId: checkpoint.orgId,
|
|
1415
|
-
version: checkpoint.version,
|
|
1416
|
-
entries: [...checkpoint.entries].map((entry) => ({
|
|
1417
|
-
userKeyPairId: entry.userKeyPairId,
|
|
1418
|
-
orgUserId: entry.orgUserId,
|
|
1419
|
-
fingerprint: entry.fingerprint,
|
|
1420
|
-
previousUserKeyPairId: entry.previousUserKeyPairId ?? null,
|
|
1421
|
-
rotationSignature: entry.rotationSignature ?? null
|
|
1422
|
-
})).sort((left, right) => left.userKeyPairId.localeCompare(right.userKeyPairId))
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
function canonicalizeUserKeyDirectoryCheckpoint(checkpoint) {
|
|
1426
|
-
return JSON.stringify(normalizeUserKeyDirectoryCheckpoint(checkpoint));
|
|
1427
|
-
}
|
|
1428
|
-
function buildUserKeyDirectoryCheckpointPayload(checkpoint) {
|
|
1429
|
-
return getCheckpointFingerprint(
|
|
1430
|
-
USER_KEY_DIRECTORY_CHECKPOINT_PREFIX,
|
|
1431
|
-
canonicalizeUserKeyDirectoryCheckpoint(checkpoint)
|
|
1432
|
-
);
|
|
1433
|
-
}
|
|
1434
|
-
function verifyUserKeyDirectoryCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1435
|
-
try {
|
|
1436
|
-
return crypto2.verify(
|
|
1437
|
-
"sha256",
|
|
1438
|
-
Buffer.from(buildUserKeyDirectoryCheckpointPayload(checkpoint), "utf8"),
|
|
1439
|
-
{
|
|
1440
|
-
key: publicKeyPem,
|
|
1441
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1442
|
-
},
|
|
1443
|
-
Buffer.from(signature, "base64")
|
|
1444
|
-
);
|
|
1445
|
-
} catch {
|
|
1446
|
-
return false;
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
function buildUserKeyDirectoryTransparencyEntryHash(entry) {
|
|
1450
|
-
return getCheckpointFingerprint(
|
|
1451
|
-
USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX,
|
|
1452
|
-
JSON.stringify({
|
|
1453
|
-
orgId: entry.orgId,
|
|
1454
|
-
version: entry.version,
|
|
1455
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1456
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1457
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1458
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1459
|
-
signature: entry.signature,
|
|
1460
|
-
previousEntryHash: entry.previousEntryHash ?? null
|
|
1461
|
-
})
|
|
1462
|
-
);
|
|
1463
|
-
}
|
|
1464
|
-
function buildUserKeyDirectoryTransparencyEntry(params) {
|
|
1465
|
-
const entryWithoutHash = {
|
|
1466
|
-
orgId: params.checkpoint.orgId,
|
|
1467
|
-
version: params.checkpoint.version,
|
|
1468
|
-
directoryCheckpointPayload: buildUserKeyDirectoryCheckpointPayload(params.checkpoint),
|
|
1469
|
-
signerUserKeyPairId: params.signerUserKeyPairId,
|
|
1470
|
-
signerOrgUserId: params.signerOrgUserId,
|
|
1471
|
-
signerFingerprint: getPublicKeyFingerprint(params.signerPublicKey),
|
|
1472
|
-
signature: params.signature,
|
|
1473
|
-
previousEntryHash: params.previousEntryHash ?? null
|
|
1474
|
-
};
|
|
1475
|
-
return {
|
|
1476
|
-
...entryWithoutHash,
|
|
1477
|
-
entryHash: buildUserKeyDirectoryTransparencyEntryHash(entryWithoutHash)
|
|
1478
|
-
};
|
|
1479
|
-
}
|
|
1480
|
-
function verifyUserKeyDirectoryTransparencyProof(params) {
|
|
1481
|
-
if (params.proof.head.version !== params.currentEntry.version || params.proof.head.hash !== params.currentEntry.entryHash) {
|
|
1482
|
-
return false;
|
|
1483
|
-
}
|
|
1484
|
-
if (!params.previousHead) {
|
|
1485
|
-
if (params.proof.entries.length === 0) {
|
|
1486
|
-
return false;
|
|
1487
|
-
}
|
|
1488
|
-
for (let index = 0; index < params.proof.entries.length; index++) {
|
|
1489
|
-
const entry = params.proof.entries[index];
|
|
1490
|
-
if (!entry) {
|
|
1491
|
-
return false;
|
|
1492
|
-
}
|
|
1493
|
-
const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
|
|
1494
|
-
orgId: entry.orgId,
|
|
1495
|
-
version: entry.version,
|
|
1496
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1497
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1498
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1499
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1500
|
-
signature: entry.signature,
|
|
1501
|
-
previousEntryHash: entry.previousEntryHash
|
|
1502
|
-
});
|
|
1503
|
-
if (expectedHash !== entry.entryHash) {
|
|
1504
|
-
return false;
|
|
1505
|
-
}
|
|
1506
|
-
if (index === 0) {
|
|
1507
|
-
continue;
|
|
1508
|
-
}
|
|
1509
|
-
const previousEntry = params.proof.entries[index - 1];
|
|
1510
|
-
if (!previousEntry) {
|
|
1511
|
-
return false;
|
|
1512
|
-
}
|
|
1513
|
-
if (entry.previousEntryHash !== previousEntry.entryHash || entry.version !== previousEntry.version + 1) {
|
|
1514
|
-
return false;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
const lastEntry = params.proof.entries[params.proof.entries.length - 1];
|
|
1518
|
-
return lastEntry?.entryHash === params.currentEntry.entryHash && lastEntry.version === params.currentEntry.version;
|
|
1519
|
-
}
|
|
1520
|
-
if (params.currentEntry.version < params.previousHead.version) {
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1523
|
-
if (params.currentEntry.version === params.previousHead.version) {
|
|
1524
|
-
return params.currentEntry.entryHash === params.previousHead.hash && params.proof.entries.length === 0;
|
|
1525
|
-
}
|
|
1526
|
-
if (params.proof.entries.length === 0) {
|
|
1527
|
-
return false;
|
|
1528
|
-
}
|
|
1529
|
-
let previousVersion = params.previousHead.version;
|
|
1530
|
-
let previousHash = params.previousHead.hash;
|
|
1531
|
-
for (const entry of params.proof.entries) {
|
|
1532
|
-
const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
|
|
1533
|
-
orgId: entry.orgId,
|
|
1534
|
-
version: entry.version,
|
|
1535
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1536
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1537
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1538
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1539
|
-
signature: entry.signature,
|
|
1540
|
-
previousEntryHash: entry.previousEntryHash
|
|
1541
|
-
});
|
|
1542
|
-
if (expectedHash !== entry.entryHash || entry.previousEntryHash !== previousHash || entry.version !== previousVersion + 1) {
|
|
1543
|
-
return false;
|
|
1544
|
-
}
|
|
1545
|
-
previousVersion = entry.version;
|
|
1546
|
-
previousHash = entry.entryHash;
|
|
1547
|
-
}
|
|
1548
|
-
return previousHash === params.currentEntry.entryHash && previousVersion === params.currentEntry.version;
|
|
1549
|
-
}
|
|
1550
|
-
function buildWrappedDekSignaturePayload(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek) {
|
|
1551
|
-
return [
|
|
1552
|
-
WRAPPED_DEK_SIGNATURE_PREFIX,
|
|
1553
|
-
vaultId,
|
|
1554
|
-
recipientKeyId,
|
|
1555
|
-
signerUserKeyPairId,
|
|
1556
|
-
String(dekVersion),
|
|
1557
|
-
getWrappedDekFingerprint(wrappedDek)
|
|
1558
|
-
].join(":");
|
|
1559
|
-
}
|
|
1560
|
-
function verifyWrappedDekSignature(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek, wrappedDekSignature, signerPublicKeyPem) {
|
|
1561
|
-
const payload = buildWrappedDekSignaturePayload(
|
|
1562
|
-
vaultId,
|
|
1563
|
-
recipientKeyId,
|
|
1564
|
-
signerUserKeyPairId,
|
|
1565
|
-
dekVersion,
|
|
1566
|
-
wrappedDek
|
|
1567
|
-
);
|
|
1568
|
-
try {
|
|
1569
|
-
return crypto2.verify(
|
|
1570
|
-
"sha256",
|
|
1571
|
-
Buffer.from(payload, "utf8"),
|
|
1572
|
-
{
|
|
1573
|
-
key: signerPublicKeyPem,
|
|
1574
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1575
|
-
},
|
|
1576
|
-
Buffer.from(wrappedDekSignature, "base64")
|
|
1577
|
-
);
|
|
1578
|
-
} catch {
|
|
1579
|
-
return false;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
function normalizeVaultSummaryCheckpoint(checkpoint) {
|
|
1583
|
-
return {
|
|
1584
|
-
vaultId: checkpoint.vaultId,
|
|
1585
|
-
version: checkpoint.version,
|
|
1586
|
-
name: checkpoint.name,
|
|
1587
|
-
dataClassification: checkpoint.dataClassification ?? null,
|
|
1588
|
-
currentDekVersion: checkpoint.currentDekVersion ?? null,
|
|
1589
|
-
items: [...checkpoint.items].map((item) => ({
|
|
1590
|
-
id: item.id,
|
|
1591
|
-
name: item.name,
|
|
1592
|
-
type: item.type ?? null,
|
|
1593
|
-
websites: [...item.websites],
|
|
1594
|
-
groupId: item.groupId ?? null
|
|
1595
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1596
|
-
groups: [...checkpoint.groups].map((group) => ({
|
|
1597
|
-
id: group.id,
|
|
1598
|
-
name: group.name,
|
|
1599
|
-
parentId: group.parentId ?? null
|
|
1600
|
-
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
1601
|
-
};
|
|
1602
|
-
}
|
|
1603
|
-
function canonicalizeVaultSummaryCheckpoint(checkpoint) {
|
|
1604
|
-
return JSON.stringify(normalizeVaultSummaryCheckpoint(checkpoint));
|
|
1605
|
-
}
|
|
1606
|
-
function buildVaultSummaryCheckpointPayload(checkpoint) {
|
|
1607
|
-
return getCheckpointFingerprint(
|
|
1608
|
-
VAULT_SUMMARY_CHECKPOINT_PREFIX,
|
|
1609
|
-
canonicalizeVaultSummaryCheckpoint(checkpoint)
|
|
1610
|
-
);
|
|
1611
|
-
}
|
|
1612
|
-
function verifyVaultSummaryCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1613
|
-
try {
|
|
1614
|
-
return crypto2.verify(
|
|
1615
|
-
"sha256",
|
|
1616
|
-
Buffer.from(buildVaultSummaryCheckpointPayload(checkpoint), "utf8"),
|
|
1617
|
-
{
|
|
1618
|
-
key: publicKeyPem,
|
|
1619
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1620
|
-
},
|
|
1621
|
-
Buffer.from(signature, "base64")
|
|
1622
|
-
);
|
|
1623
|
-
} catch {
|
|
1624
|
-
return false;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
function normalizeVaultItemDetailCheckpoint(checkpoint) {
|
|
1628
|
-
return {
|
|
1629
|
-
vaultItemId: checkpoint.vaultItemId,
|
|
1630
|
-
vaultId: checkpoint.vaultId,
|
|
1631
|
-
version: checkpoint.version,
|
|
1632
|
-
name: checkpoint.name,
|
|
1633
|
-
type: checkpoint.type ?? null,
|
|
1634
|
-
websites: [...checkpoint.websites],
|
|
1635
|
-
groupId: checkpoint.groupId ?? null,
|
|
1636
|
-
fields: [...checkpoint.fields].map((field) => ({
|
|
1637
|
-
id: field.id,
|
|
1638
|
-
name: field.name,
|
|
1639
|
-
type: field.type,
|
|
1640
|
-
order: field.order,
|
|
1641
|
-
fieldInstanceIds: [...field.fieldInstanceIds].sort(),
|
|
1642
|
-
assetIds: [...field.assetIds].sort()
|
|
1643
|
-
})).sort((left, right) => left.order - right.order || left.id.localeCompare(right.id))
|
|
1644
|
-
};
|
|
1645
|
-
}
|
|
1646
|
-
function canonicalizeVaultItemDetailCheckpoint(checkpoint) {
|
|
1647
|
-
return JSON.stringify(normalizeVaultItemDetailCheckpoint(checkpoint));
|
|
1648
|
-
}
|
|
1649
|
-
function buildVaultItemDetailCheckpointPayload(checkpoint) {
|
|
1650
|
-
return getCheckpointFingerprint(
|
|
1651
|
-
VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX,
|
|
1652
|
-
canonicalizeVaultItemDetailCheckpoint(checkpoint)
|
|
1653
|
-
);
|
|
1654
|
-
}
|
|
1655
|
-
function verifyVaultItemDetailCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1656
|
-
try {
|
|
1657
|
-
return crypto2.verify(
|
|
1658
|
-
"sha256",
|
|
1659
|
-
Buffer.from(buildVaultItemDetailCheckpointPayload(checkpoint), "utf8"),
|
|
1660
|
-
{
|
|
1661
|
-
key: publicKeyPem,
|
|
1662
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1663
|
-
},
|
|
1664
|
-
Buffer.from(signature, "base64")
|
|
1665
|
-
);
|
|
1666
|
-
} catch {
|
|
1667
|
-
return false;
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
function isVaultEnvelope(value) {
|
|
1671
|
-
if (!value.startsWith("{")) {
|
|
1672
|
-
return false;
|
|
1673
|
-
}
|
|
1674
|
-
try {
|
|
1675
|
-
const parsed = JSON.parse(value);
|
|
1676
|
-
return parsed.v === 3;
|
|
1677
|
-
} catch {
|
|
1678
|
-
return false;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
function decryptWithVaultDEK(encryptedValue, dek) {
|
|
1682
|
-
if (!isVaultEnvelope(encryptedValue)) {
|
|
1683
|
-
throw new Error("Invalid encrypted value: expected v3 vault envelope format");
|
|
1684
|
-
}
|
|
1685
|
-
const envelope = JSON.parse(encryptedValue);
|
|
1686
|
-
const decipher = crypto2.createDecipheriv("aes-256-gcm", dek, Buffer.from(envelope.iv, "base64"));
|
|
1687
|
-
decipher.setAuthTag(Buffer.from(envelope.t, "base64"));
|
|
1688
|
-
const decrypted = Buffer.concat([
|
|
1689
|
-
decipher.update(Buffer.from(envelope.d, "base64")),
|
|
1690
|
-
decipher.final()
|
|
1691
|
-
]);
|
|
1692
|
-
return decrypted.toString("utf8");
|
|
1693
|
-
}
|
|
1694
|
-
function decryptStoredFieldValue(value, dek) {
|
|
1695
|
-
return isVaultEnvelope(value) ? decryptWithVaultDEK(value, dek) : value;
|
|
1696
|
-
}
|
|
1697
|
-
function unwrapDEKWithPrivateKey(wrappedDek, privateKeyPem) {
|
|
1698
|
-
return crypto2.privateDecrypt(
|
|
1699
|
-
{ key: privateKeyPem, ...RSA_OAEP_CONFIG },
|
|
1700
|
-
Buffer.from(wrappedDek, "base64")
|
|
1701
|
-
);
|
|
1702
|
-
}
|
|
1703
|
-
function loadTrustStore(trustStorePath) {
|
|
1704
|
-
try {
|
|
1705
|
-
const raw = fs22.readFileSync(trustStorePath, "utf8");
|
|
1706
|
-
const parsed = JSON.parse(raw);
|
|
1707
|
-
return {
|
|
1708
|
-
version: 1,
|
|
1709
|
-
userKeyPins: parsed.userKeyPins ?? {},
|
|
1710
|
-
checkpointVersionPins: parsed.checkpointVersionPins ?? {},
|
|
1711
|
-
transparencyHeadPins: parsed.transparencyHeadPins ?? {}
|
|
1712
|
-
};
|
|
1713
|
-
} catch {
|
|
1714
|
-
return {
|
|
1715
|
-
version: 1,
|
|
1716
|
-
userKeyPins: {},
|
|
1717
|
-
checkpointVersionPins: {},
|
|
1718
|
-
transparencyHeadPins: {}
|
|
1719
|
-
};
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
|
-
function saveTrustStore(trustStorePath, store) {
|
|
1723
|
-
fs22.mkdirSync(path22.dirname(trustStorePath), { recursive: true });
|
|
1724
|
-
fs22.writeFileSync(trustStorePath, JSON.stringify(store, null, 2) + "\n", "utf8");
|
|
1725
|
-
}
|
|
1726
|
-
function getPinStorageKey(orgId, orgUserId) {
|
|
1727
|
-
return `${orgId}:${orgUserId}`;
|
|
1728
|
-
}
|
|
1729
|
-
function getAgentPinStorageKey(orgId, agentId) {
|
|
1730
|
-
return `agent:${agentId}`;
|
|
1731
|
-
}
|
|
1732
|
-
function getDirectoryPinStorageKey(orgId) {
|
|
1733
|
-
return `org:${orgId}`;
|
|
1734
|
-
}
|
|
1735
|
-
function getAgentTransparencyPinStorageKey(orgId, agentId) {
|
|
1736
|
-
return `agent-head:${agentId}`;
|
|
1737
|
-
}
|
|
1738
|
-
async function fetchWitnessArtifact(witnessBaseUrl, pathName) {
|
|
1739
|
-
const response = await fetch(
|
|
1740
|
-
buildTransparencyWitnessUrl(witnessBaseUrl, pathName),
|
|
1741
|
-
{
|
|
1742
|
-
cache: "no-store"
|
|
1743
|
-
}
|
|
1744
|
-
);
|
|
1745
|
-
if (!response.ok) {
|
|
1746
|
-
throw new Error(
|
|
1747
|
-
`Failed to fetch public transparency witness artifact (${response.status}). Production first-trust bootstrapping needs access to https://transparency.r4.dev. If this is a dev environment, re-run with --dev or set R4_DEV=1. If this is production, verify outbound access to transparency.r4.dev and retry.`
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
|
-
return response.json();
|
|
1751
|
-
}
|
|
1752
|
-
function getSinglePinnedTransparencyHead(trustStorePath) {
|
|
1753
|
-
const store = loadTrustStore(trustStorePath);
|
|
1754
|
-
const heads = Object.values(store.transparencyHeadPins);
|
|
1755
|
-
return heads.length === 1 ? heads[0] : null;
|
|
1756
|
-
}
|
|
1757
|
-
async function getPublicOrgWitnessHead(apiBaseUrl, orgId, configuredWitnessBaseUrl) {
|
|
1758
|
-
const witnessBaseUrl = resolveTransparencyWitnessBaseUrl({
|
|
1759
|
-
apiBaseUrl,
|
|
1760
|
-
configuredBaseUrl: configuredWitnessBaseUrl
|
|
1761
|
-
});
|
|
1762
|
-
if (!witnessBaseUrl) {
|
|
1763
|
-
return null;
|
|
1764
|
-
}
|
|
1765
|
-
const artifact = await fetchWitnessArtifact(
|
|
1766
|
-
witnessBaseUrl,
|
|
1767
|
-
buildOrgUserKeyDirectoryWitnessPath(orgId)
|
|
1768
|
-
);
|
|
1769
|
-
if (artifact.kind !== "org-user-key-directory" || artifact.orgId !== orgId || !verifyOrgUserKeyDirectoryWitnessArtifact(
|
|
1770
|
-
artifact,
|
|
1771
|
-
TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM
|
|
1772
|
-
)) {
|
|
1773
|
-
throw new Error(`Public transparency witness verification failed for org ${orgId}.`);
|
|
1774
|
-
}
|
|
1775
|
-
return artifact.head;
|
|
1776
|
-
}
|
|
1777
|
-
async function getPublicAgentWitnessHead(apiBaseUrl, agentId, configuredWitnessBaseUrl) {
|
|
1778
|
-
const witnessBaseUrl = resolveTransparencyWitnessBaseUrl({
|
|
1779
|
-
apiBaseUrl,
|
|
1780
|
-
configuredBaseUrl: configuredWitnessBaseUrl
|
|
1781
|
-
});
|
|
1782
|
-
if (!witnessBaseUrl) {
|
|
1783
|
-
return null;
|
|
1784
|
-
}
|
|
1785
|
-
const artifact = await fetchWitnessArtifact(
|
|
1786
|
-
witnessBaseUrl,
|
|
1787
|
-
buildAgentPublicKeyWitnessPath(agentId)
|
|
1788
|
-
);
|
|
1789
|
-
if (artifact.kind !== "agent-public-key" || artifact.agentId !== agentId || !verifyAgentPublicKeyWitnessArtifact(
|
|
1790
|
-
artifact,
|
|
1791
|
-
TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM
|
|
1792
|
-
)) {
|
|
1793
|
-
throw new Error(`Public transparency witness verification failed for agent ${agentId}.`);
|
|
1794
|
-
}
|
|
1795
|
-
return artifact.head;
|
|
1796
|
-
}
|
|
1797
|
-
async function pinVaultUserPublicKeys(trustStorePath, orgId, publicKeys) {
|
|
1798
|
-
const store = loadTrustStore(trustStorePath);
|
|
1799
|
-
let changed = false;
|
|
1800
|
-
for (const key of publicKeys) {
|
|
1801
|
-
const storageKey = getPinStorageKey(orgId, key.orgUserId);
|
|
1802
|
-
const computedFingerprint = getPublicKeyFingerprint(key.publicKey);
|
|
1803
|
-
if (computedFingerprint !== key.fingerprint) {
|
|
1804
|
-
throw new Error(`Server returned a mismatched fingerprint for user ${key.orgUserId}.`);
|
|
1805
|
-
}
|
|
1806
|
-
const existing = store.userKeyPins[storageKey];
|
|
1807
|
-
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1808
|
-
if (!existing) {
|
|
1809
|
-
store.userKeyPins[storageKey] = {
|
|
1810
|
-
keyPairId: key.userKeyPairId,
|
|
1811
|
-
fingerprint: key.fingerprint,
|
|
1812
|
-
publicKey: key.publicKey,
|
|
1813
|
-
pinnedAt: verifiedAt,
|
|
1814
|
-
verifiedAt
|
|
1815
|
-
};
|
|
1816
|
-
changed = true;
|
|
1817
|
-
continue;
|
|
1818
|
-
}
|
|
1819
|
-
if (existing.keyPairId === key.userKeyPairId) {
|
|
1820
|
-
if (existing.fingerprint !== key.fingerprint || existing.publicKey !== key.publicKey) {
|
|
1821
|
-
throw new Error(
|
|
1822
|
-
`Pinned public key ${key.userKeyPairId} changed unexpectedly for user ${key.orgUserId}.`
|
|
1823
|
-
);
|
|
1824
|
-
}
|
|
1825
|
-
if (existing.verifiedAt !== verifiedAt) {
|
|
1826
|
-
store.userKeyPins[storageKey] = {
|
|
1827
|
-
...existing,
|
|
1828
|
-
verifiedAt
|
|
1829
|
-
};
|
|
1830
|
-
changed = true;
|
|
1831
|
-
}
|
|
1832
|
-
continue;
|
|
1833
|
-
}
|
|
1834
|
-
if (!key.previousUserKeyPairId || key.previousUserKeyPairId !== existing.keyPairId || !key.rotationSignature) {
|
|
1835
|
-
throw new Error(`Public key rotation for user ${key.orgUserId} is missing a trusted continuity proof.`);
|
|
1836
|
-
}
|
|
1837
|
-
const rotationVerified = verifyUserKeyRotation(
|
|
1838
|
-
existing.keyPairId,
|
|
1839
|
-
key.publicKey,
|
|
1840
|
-
key.rotationSignature,
|
|
1841
|
-
existing.publicKey
|
|
1842
|
-
);
|
|
1843
|
-
if (!rotationVerified) {
|
|
1844
|
-
throw new Error(`Public key rotation for user ${key.orgUserId} failed signature verification.`);
|
|
1845
|
-
}
|
|
1846
|
-
store.userKeyPins[storageKey] = {
|
|
1847
|
-
keyPairId: key.userKeyPairId,
|
|
1848
|
-
fingerprint: key.fingerprint,
|
|
1849
|
-
publicKey: key.publicKey,
|
|
1850
|
-
pinnedAt: existing.pinnedAt,
|
|
1851
|
-
verifiedAt
|
|
1852
|
-
};
|
|
1853
|
-
changed = true;
|
|
1854
|
-
}
|
|
1855
|
-
if (changed) {
|
|
1856
|
-
saveTrustStore(trustStorePath, store);
|
|
1857
|
-
}
|
|
1858
|
-
return publicKeys;
|
|
1859
|
-
}
|
|
1860
|
-
async function pinVaultAgentPublicKeys(trustStorePath, orgId, publicKeys, apiBaseUrl, configuredWitnessBaseUrl) {
|
|
1861
|
-
const store = loadTrustStore(trustStorePath);
|
|
1862
|
-
let changed = false;
|
|
1863
|
-
for (const key of publicKeys) {
|
|
1864
|
-
const storageKey = getAgentPinStorageKey(orgId, key.agentId);
|
|
1865
|
-
const computedFingerprint = getPublicKeyFingerprint(key.publicKey);
|
|
1866
|
-
if (computedFingerprint !== key.fingerprint) {
|
|
1867
|
-
throw new Error(`Server returned a mismatched fingerprint for agent ${key.agentId}.`);
|
|
1868
|
-
}
|
|
1869
|
-
if (!key.transparency) {
|
|
1870
|
-
throw new Error(`Server omitted the agent public-key transparency proof for agent ${key.agentId}.`);
|
|
1871
|
-
}
|
|
1872
|
-
const currentProofEntry = key.transparency.entries[key.transparency.entries.length - 1];
|
|
1873
|
-
if (!currentProofEntry) {
|
|
1874
|
-
throw new Error(`Server returned an empty agent public-key transparency proof for agent ${key.agentId}.`);
|
|
1875
|
-
}
|
|
1876
|
-
const expectedCurrentEntry = buildAgentPublicKeyTransparencyEntry({
|
|
1877
|
-
agentId: key.agentId,
|
|
1878
|
-
version: currentProofEntry.version,
|
|
1879
|
-
encryptionKeyId: key.encryptionKeyId,
|
|
1880
|
-
publicKey: key.publicKey,
|
|
1881
|
-
fingerprint: key.fingerprint,
|
|
1882
|
-
previousEncryptionKeyId: key.previousEncryptionKeyId ?? null,
|
|
1883
|
-
rotationSignature: key.rotationSignature ?? null,
|
|
1884
|
-
previousEntryHash: currentProofEntry.previousEntryHash ?? null
|
|
1885
|
-
});
|
|
1886
|
-
if (expectedCurrentEntry.entryHash !== currentProofEntry.entryHash) {
|
|
1887
|
-
throw new Error(`Agent public-key transparency entry does not match the current key for agent ${key.agentId}.`);
|
|
1888
|
-
}
|
|
1889
|
-
const pinnedTransparencyHead = store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] ?? null;
|
|
1890
|
-
const witnessHead = !pinnedTransparencyHead ? await getPublicAgentWitnessHead(apiBaseUrl, key.agentId, configuredWitnessBaseUrl) : null;
|
|
1891
|
-
const trustedPreviousHead = witnessHead ?? pinnedTransparencyHead;
|
|
1892
|
-
if (witnessHead && key.transparency.head.version < witnessHead.version) {
|
|
1893
|
-
throw new Error(`Public transparency witness head is ahead of the server response for agent ${key.agentId}.`);
|
|
1894
|
-
}
|
|
1895
|
-
if (witnessHead && key.transparency.head.version === witnessHead.version) {
|
|
1896
|
-
if (key.transparency.head.hash !== witnessHead.hash) {
|
|
1897
|
-
throw new Error(`Public transparency witness head fork detected for agent ${key.agentId}.`);
|
|
1898
|
-
}
|
|
1899
|
-
if (currentProofEntry.entryHash !== witnessHead.hash || expectedCurrentEntry.entryHash !== witnessHead.hash) {
|
|
1900
|
-
throw new Error(`Agent public-key transparency witness anchor mismatch for agent ${key.agentId}.`);
|
|
1901
|
-
}
|
|
1902
|
-
} else if (!verifyAgentPublicKeyTransparencyProof({
|
|
1903
|
-
currentEntry: expectedCurrentEntry,
|
|
1904
|
-
proof: key.transparency,
|
|
1905
|
-
previousHead: trustedPreviousHead
|
|
1906
|
-
})) {
|
|
1907
|
-
throw new Error(`Agent public-key transparency proof verification failed for agent ${key.agentId}.`);
|
|
1908
|
-
}
|
|
1909
|
-
const existing = store.userKeyPins[storageKey];
|
|
1910
|
-
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1911
|
-
if (!existing) {
|
|
1912
|
-
store.userKeyPins[storageKey] = {
|
|
1913
|
-
keyPairId: key.encryptionKeyId,
|
|
1914
|
-
fingerprint: key.fingerprint,
|
|
1915
|
-
publicKey: key.publicKey,
|
|
1916
|
-
pinnedAt: verifiedAt,
|
|
1917
|
-
verifiedAt
|
|
1918
|
-
};
|
|
1919
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1920
|
-
changed = true;
|
|
1921
|
-
continue;
|
|
1922
|
-
}
|
|
1923
|
-
if (existing.keyPairId === key.encryptionKeyId) {
|
|
1924
|
-
if (existing.fingerprint !== key.fingerprint || existing.publicKey !== key.publicKey) {
|
|
1925
|
-
throw new Error(
|
|
1926
|
-
`Pinned public key ${key.encryptionKeyId} changed unexpectedly for agent ${key.agentId}.`
|
|
1927
|
-
);
|
|
1928
|
-
}
|
|
1929
|
-
if (existing.verifiedAt !== verifiedAt || store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)]?.version !== key.transparency.head.version || store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)]?.hash !== key.transparency.head.hash) {
|
|
1930
|
-
store.userKeyPins[storageKey] = {
|
|
1931
|
-
...existing,
|
|
1932
|
-
verifiedAt
|
|
1933
|
-
};
|
|
1934
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1935
|
-
changed = true;
|
|
1936
|
-
}
|
|
1937
|
-
continue;
|
|
1938
|
-
}
|
|
1939
|
-
if (!key.previousEncryptionKeyId || key.previousEncryptionKeyId !== existing.keyPairId || !key.rotationSignature) {
|
|
1940
|
-
throw new Error(`Public key rotation for agent ${key.agentId} is missing a trusted continuity proof.`);
|
|
1941
|
-
}
|
|
1942
|
-
const rotationVerified = verifyUserKeyRotation(
|
|
1943
|
-
existing.keyPairId,
|
|
1944
|
-
key.publicKey,
|
|
1945
|
-
key.rotationSignature,
|
|
1946
|
-
existing.publicKey
|
|
1947
|
-
);
|
|
1948
|
-
if (!rotationVerified) {
|
|
1949
|
-
throw new Error(`Public key rotation for agent ${key.agentId} failed signature verification.`);
|
|
1950
|
-
}
|
|
1951
|
-
store.userKeyPins[storageKey] = {
|
|
1952
|
-
keyPairId: key.encryptionKeyId,
|
|
1953
|
-
fingerprint: key.fingerprint,
|
|
1954
|
-
publicKey: key.publicKey,
|
|
1955
|
-
pinnedAt: existing.pinnedAt,
|
|
1956
|
-
verifiedAt
|
|
1957
|
-
};
|
|
1958
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1959
|
-
changed = true;
|
|
1960
|
-
}
|
|
1961
|
-
if (changed) {
|
|
1962
|
-
saveTrustStore(trustStorePath, store);
|
|
1963
|
-
}
|
|
1964
|
-
return publicKeys;
|
|
1965
|
-
}
|
|
1966
|
-
async function verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead) {
|
|
1967
|
-
if (!directory.directoryCheckpoint) {
|
|
1968
|
-
if (directory.publicKeys.length > 0) {
|
|
1969
|
-
throw new Error("Server omitted the user-key directory checkpoint for a non-empty vault signer directory.");
|
|
1970
|
-
}
|
|
1971
|
-
return null;
|
|
1972
|
-
}
|
|
1973
|
-
const { directoryCheckpoint } = directory;
|
|
1974
|
-
const orgId = directoryCheckpoint.checkpoint.orgId;
|
|
1975
|
-
if (!directoryCheckpoint.signerOrgUserId || !directoryCheckpoint.signerPublicKey) {
|
|
1976
|
-
throw new Error("Server returned an incomplete user-key directory signer payload.");
|
|
1977
|
-
}
|
|
1978
|
-
const signerFingerprint = getPublicKeyFingerprint(directoryCheckpoint.signerPublicKey);
|
|
1979
|
-
const signerEntry = directoryCheckpoint.checkpoint.entries.find(
|
|
1980
|
-
(entry) => entry.userKeyPairId === directoryCheckpoint.signerUserKeyPairId && entry.orgUserId === directoryCheckpoint.signerOrgUserId
|
|
1981
|
-
);
|
|
1982
|
-
const signerKey = {
|
|
1983
|
-
userKeyPairId: directoryCheckpoint.signerUserKeyPairId,
|
|
1984
|
-
orgUserId: directoryCheckpoint.signerOrgUserId,
|
|
1985
|
-
publicKey: directoryCheckpoint.signerPublicKey,
|
|
1986
|
-
fingerprint: signerFingerprint,
|
|
1987
|
-
previousUserKeyPairId: signerEntry?.previousUserKeyPairId ?? null,
|
|
1988
|
-
rotationSignature: signerEntry?.rotationSignature ?? null
|
|
1989
|
-
};
|
|
1990
|
-
const storeBeforeVerification = loadTrustStore(trustStorePath);
|
|
1991
|
-
try {
|
|
1992
|
-
await pinVaultUserPublicKeys(trustStorePath, orgId, [signerKey]);
|
|
1993
|
-
const verified = verifyUserKeyDirectoryCheckpoint(
|
|
1994
|
-
directoryCheckpoint.checkpoint,
|
|
1995
|
-
directoryCheckpoint.signature,
|
|
1996
|
-
directoryCheckpoint.signerPublicKey
|
|
1997
|
-
);
|
|
1998
|
-
if (!verified) {
|
|
1999
|
-
throw new Error(`User-key directory signature verification failed for org ${orgId}.`);
|
|
2000
|
-
}
|
|
2001
|
-
if (!directory.transparency) {
|
|
2002
|
-
throw new Error(`Server omitted the user-key directory transparency proof for org ${orgId}.`);
|
|
2003
|
-
}
|
|
2004
|
-
const store = loadTrustStore(trustStorePath);
|
|
2005
|
-
const pinnedTransparencyHead = store.transparencyHeadPins[getDirectoryPinStorageKey(orgId)] ?? null;
|
|
2006
|
-
const trustedPreviousHead = anchorHead ?? pinnedTransparencyHead;
|
|
2007
|
-
const legacyPinnedVersion = store.checkpointVersionPins[getDirectoryPinStorageKey(orgId)] ?? null;
|
|
2008
|
-
if (!trustedPreviousHead && legacyPinnedVersion !== null && directory.transparency.head.version < legacyPinnedVersion) {
|
|
2009
|
-
throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
|
|
2010
|
-
}
|
|
2011
|
-
if (!trustedPreviousHead && directory.transparency.entries.length === 0) {
|
|
2012
|
-
throw new Error(`Server omitted the current transparency entry for org ${orgId}.`);
|
|
2013
|
-
}
|
|
2014
|
-
if (directory.transparency.entries.length > 0) {
|
|
2015
|
-
const currentProofEntry = directory.transparency.entries[directory.transparency.entries.length - 1];
|
|
2016
|
-
if (!currentProofEntry) {
|
|
2017
|
-
throw new Error(`Server returned an empty transparency proof for org ${orgId}.`);
|
|
2018
|
-
}
|
|
2019
|
-
const expectedCurrentEntry = buildUserKeyDirectoryTransparencyEntry({
|
|
2020
|
-
checkpoint: directoryCheckpoint.checkpoint,
|
|
2021
|
-
signerUserKeyPairId: directoryCheckpoint.signerUserKeyPairId,
|
|
2022
|
-
signerOrgUserId: directoryCheckpoint.signerOrgUserId,
|
|
2023
|
-
signerPublicKey: directoryCheckpoint.signerPublicKey,
|
|
2024
|
-
signature: directoryCheckpoint.signature,
|
|
2025
|
-
previousEntryHash: currentProofEntry.previousEntryHash ?? null
|
|
2026
|
-
});
|
|
2027
|
-
if (expectedCurrentEntry.entryHash !== currentProofEntry.entryHash) {
|
|
2028
|
-
throw new Error(`User-key transparency entry does not match the signed directory for org ${orgId}.`);
|
|
2029
|
-
}
|
|
2030
|
-
if (anchorHead && directory.transparency.head.version === anchorHead.version) {
|
|
2031
|
-
if (directory.transparency.head.hash !== anchorHead.hash) {
|
|
2032
|
-
throw new Error(`Public transparency witness head fork detected for org ${orgId}.`);
|
|
2033
|
-
}
|
|
2034
|
-
if (currentProofEntry.entryHash !== anchorHead.hash || expectedCurrentEntry.entryHash !== anchorHead.hash) {
|
|
2035
|
-
throw new Error(`User-key transparency witness anchor mismatch for org ${orgId}.`);
|
|
2036
|
-
}
|
|
2037
|
-
} else if (!verifyUserKeyDirectoryTransparencyProof({
|
|
2038
|
-
currentEntry: expectedCurrentEntry,
|
|
2039
|
-
proof: directory.transparency,
|
|
2040
|
-
previousHead: trustedPreviousHead
|
|
2041
|
-
})) {
|
|
2042
|
-
throw new Error(`User-key transparency proof verification failed for org ${orgId}.`);
|
|
2043
|
-
}
|
|
2044
|
-
} else if (anchorHead && directory.transparency.head.version === anchorHead.version) {
|
|
2045
|
-
throw new Error(`Server omitted the current transparency entry required to verify org ${orgId} against the public witness.`);
|
|
2046
|
-
} else if (!trustedPreviousHead || trustedPreviousHead.version !== directory.transparency.head.version || trustedPreviousHead.hash !== directory.transparency.head.hash) {
|
|
2047
|
-
throw new Error(`Server returned an incomplete user-key transparency proof for org ${orgId}.`);
|
|
2048
|
-
}
|
|
2049
|
-
assertAndPinTransparencyHead(trustStorePath, orgId, directory.transparency.head);
|
|
2050
|
-
const checkpointEntries = new Map(
|
|
2051
|
-
directoryCheckpoint.checkpoint.entries.map((entry) => [entry.userKeyPairId, entry])
|
|
2052
|
-
);
|
|
2053
|
-
for (const key of directory.publicKeys) {
|
|
2054
|
-
const entry = checkpointEntries.get(key.userKeyPairId);
|
|
2055
|
-
if (!entry) {
|
|
2056
|
-
throw new Error(`User key ${key.userKeyPairId} is missing from the signed org directory.`);
|
|
2057
|
-
}
|
|
2058
|
-
if (entry.orgUserId !== key.orgUserId || entry.fingerprint !== key.fingerprint || entry.previousUserKeyPairId !== key.previousUserKeyPairId || entry.rotationSignature !== key.rotationSignature) {
|
|
2059
|
-
throw new Error(`User key ${key.userKeyPairId} does not match the signed org directory.`);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
return orgId;
|
|
2063
|
-
} catch (error) {
|
|
2064
|
-
saveTrustStore(trustStorePath, storeBeforeVerification);
|
|
2065
|
-
throw error;
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
async function verifyAndPinVaultUserPublicKeys(trustStorePath, directory, anchorHead) {
|
|
2069
|
-
const orgId = await verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead);
|
|
2070
|
-
if (!orgId) {
|
|
2071
|
-
return directory.publicKeys;
|
|
2072
|
-
}
|
|
2073
|
-
return pinVaultUserPublicKeys(trustStorePath, orgId, directory.publicKeys);
|
|
2074
|
-
}
|
|
2075
|
-
async function verifyAndPinVaultAgentPublicKeys(trustStorePath, orgId, publicKeys, apiBaseUrl, configuredWitnessBaseUrl) {
|
|
2076
|
-
return pinVaultAgentPublicKeys(
|
|
2077
|
-
trustStorePath,
|
|
2078
|
-
orgId,
|
|
2079
|
-
publicKeys,
|
|
2080
|
-
apiBaseUrl,
|
|
2081
|
-
configuredWitnessBaseUrl
|
|
2082
|
-
);
|
|
2083
|
-
}
|
|
2084
|
-
function assertAndPinCheckpointVersion(trustStorePath, storageKey, version) {
|
|
2085
|
-
const store = loadTrustStore(trustStorePath);
|
|
2086
|
-
const pinnedVersion = store.checkpointVersionPins[storageKey];
|
|
2087
|
-
if (pinnedVersion !== void 0 && version < pinnedVersion) {
|
|
2088
|
-
throw new Error(`Checkpoint version rolled back unexpectedly for ${storageKey}.`);
|
|
2089
|
-
}
|
|
2090
|
-
if (pinnedVersion === void 0 || version > pinnedVersion) {
|
|
2091
|
-
store.checkpointVersionPins[storageKey] = version;
|
|
2092
|
-
saveTrustStore(trustStorePath, store);
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
function assertAndPinTransparencyHead(trustStorePath, orgId, head) {
|
|
2096
|
-
const store = loadTrustStore(trustStorePath);
|
|
2097
|
-
const storageKey = getDirectoryPinStorageKey(orgId);
|
|
2098
|
-
const pinnedHead = store.transparencyHeadPins[storageKey];
|
|
2099
|
-
if (pinnedHead) {
|
|
2100
|
-
if (head.version < pinnedHead.version) {
|
|
2101
|
-
throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
|
|
2102
|
-
}
|
|
2103
|
-
if (head.version === pinnedHead.version && head.hash !== pinnedHead.hash) {
|
|
2104
|
-
throw new Error(`User-key transparency head fork detected for org ${orgId}.`);
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
if (!pinnedHead || head.version > pinnedHead.version) {
|
|
2108
|
-
store.transparencyHeadPins[storageKey] = head;
|
|
2109
|
-
saveTrustStore(trustStorePath, store);
|
|
2110
|
-
}
|
|
2111
|
-
assertAndPinCheckpointVersion(trustStorePath, storageKey, head.version);
|
|
2112
|
-
}
|
|
2113
|
-
var R4_DEFAULT_API_BASE_URL2 = "https://r4.dev";
|
|
2114
|
-
var R4_DEV_API_BASE_URL2 = "https://dev.r4.dev";
|
|
2115
|
-
function toScreamingSnakeCase(input) {
|
|
2116
|
-
return input.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
|
|
2117
|
-
}
|
|
2118
|
-
function resolveTrustStorePath2(config) {
|
|
2119
|
-
if (config.trustStorePath) {
|
|
2120
|
-
return path3.resolve(config.trustStorePath);
|
|
2121
|
-
}
|
|
2122
|
-
if (config.privateKeyPath) {
|
|
2123
|
-
return `${path3.resolve(config.privateKeyPath)}.trust.json`;
|
|
2124
|
-
}
|
|
2125
|
-
return path3.resolve(process.cwd(), ".r4-trust-store.json");
|
|
2126
|
-
}
|
|
2127
|
-
function resolveApiBaseUrl(config) {
|
|
2128
|
-
if (config.baseUrl) {
|
|
2129
|
-
return config.baseUrl;
|
|
2130
|
-
}
|
|
2131
|
-
return config.dev ? R4_DEV_API_BASE_URL2 : R4_DEFAULT_API_BASE_URL2;
|
|
2132
|
-
}
|
|
2133
|
-
function buildVaultSummaryCheckpointFromListResponse(response, version) {
|
|
2134
|
-
return {
|
|
2135
|
-
vaultId: response.vaultId,
|
|
2136
|
-
version,
|
|
2137
|
-
name: response.vaultName,
|
|
2138
|
-
dataClassification: response.dataClassification ?? null,
|
|
2139
|
-
currentDekVersion: response.currentDekVersion ?? null,
|
|
2140
|
-
items: response.items.map((item) => ({
|
|
2141
|
-
id: item.id,
|
|
2142
|
-
name: item.name,
|
|
2143
|
-
type: item.type ?? null,
|
|
2144
|
-
websites: item.websites ?? [],
|
|
2145
|
-
groupId: item.groupId ?? null
|
|
2146
|
-
})),
|
|
2147
|
-
groups: response.vaultItemGroups.map((group) => ({
|
|
2148
|
-
id: group.id,
|
|
2149
|
-
name: group.name,
|
|
2150
|
-
parentId: group.parentId ?? null
|
|
2151
|
-
}))
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2154
|
-
function buildVaultItemDetailCheckpointFromResponse(item, version) {
|
|
2155
|
-
return {
|
|
2156
|
-
vaultItemId: item.id,
|
|
2157
|
-
vaultId: item.vaultId,
|
|
2158
|
-
version,
|
|
2159
|
-
name: item.name,
|
|
2160
|
-
type: item.type ?? null,
|
|
2161
|
-
websites: item.websites ?? [],
|
|
2162
|
-
groupId: item.groupId ?? null,
|
|
2163
|
-
fields: item.fields.map((field, index) => ({
|
|
2164
|
-
id: field.id,
|
|
2165
|
-
name: field.name,
|
|
2166
|
-
type: field.type,
|
|
2167
|
-
order: field.order ?? index,
|
|
2168
|
-
fieldInstanceIds: field.fieldInstanceIds ?? [],
|
|
2169
|
-
assetIds: field.assetIds ?? []
|
|
2170
|
-
}))
|
|
2171
|
-
};
|
|
2172
|
-
}
|
|
2173
|
-
function getCheckpointSignerPublicKey(params) {
|
|
2174
|
-
const signerType = params.checkpoint.signerType ?? "USER_KEY_PAIR";
|
|
2175
|
-
if (signerType === "AGENT_ENCRYPTION_KEY") {
|
|
2176
|
-
const signerKey2 = params.agentKeys.find(
|
|
2177
|
-
(publicKey) => publicKey.encryptionKeyId === params.checkpoint.signerEncryptionKeyId
|
|
2178
|
-
);
|
|
2179
|
-
const historicalSignerKey = params.agentKeys.flatMap((publicKey) => publicKey.transparency?.entries ?? []).find((entry) => entry.encryptionKeyId === params.checkpoint.signerEncryptionKeyId);
|
|
2180
|
-
if (!signerKey2 && !historicalSignerKey) {
|
|
2181
|
-
throw new Error(
|
|
2182
|
-
`R4 SDK: checkpoint signer ${params.checkpoint.signerEncryptionKeyId ?? "unknown"} was not present in the trusted agent key directory.`
|
|
2183
|
-
);
|
|
2184
|
-
}
|
|
2185
|
-
return signerKey2?.publicKey ?? historicalSignerKey.publicKey;
|
|
2186
|
-
}
|
|
2187
|
-
const signerKey = params.userKeys.find(
|
|
2188
|
-
(publicKey) => publicKey.userKeyPairId === params.checkpoint.signerUserKeyPairId
|
|
2189
|
-
);
|
|
2190
|
-
if (!signerKey) {
|
|
2191
|
-
throw new Error(
|
|
2192
|
-
`R4 SDK: checkpoint signer ${params.checkpoint.signerUserKeyPairId ?? "unknown"} was not present in the trusted user key directory.`
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2195
|
-
return signerKey.publicKey;
|
|
2196
|
-
}
|
|
2197
|
-
var R4 = class _R4 {
|
|
2198
|
-
client;
|
|
2199
|
-
baseUrl;
|
|
2200
|
-
transparencyWitnessUrl;
|
|
2201
|
-
projectId;
|
|
2202
|
-
privateKeyPem;
|
|
2203
|
-
publicKeyPem;
|
|
2204
|
-
trustStorePath;
|
|
2205
|
-
_env = null;
|
|
2206
|
-
constructor(config) {
|
|
2207
|
-
if (!config.apiKey) {
|
|
2208
|
-
throw new Error("R4 SDK: apiKey is required");
|
|
2209
|
-
}
|
|
2210
|
-
if (!config.privateKey && !config.privateKeyPath) {
|
|
2211
|
-
throw new Error(
|
|
2212
|
-
"R4 SDK: privateKey or privateKeyPath is required for zero-trust local decryption."
|
|
2213
|
-
);
|
|
2214
|
-
}
|
|
2215
|
-
const baseUrl = resolveApiBaseUrl(config);
|
|
2216
|
-
this.baseUrl = baseUrl;
|
|
2217
|
-
this.transparencyWitnessUrl = config.transparencyWitnessUrl;
|
|
2218
|
-
this.client = new R4Client(config.apiKey, baseUrl);
|
|
2219
|
-
this.projectId = config.projectId;
|
|
2220
|
-
this.privateKeyPem = config.privateKey ?? loadPrivateKey2(config.privateKeyPath);
|
|
2221
|
-
this.publicKeyPem = derivePublicKey2(this.privateKeyPem);
|
|
2222
|
-
this.trustStorePath = resolveTrustStorePath2(config);
|
|
2223
|
-
}
|
|
2224
|
-
/**
|
|
2225
|
-
* Static factory method that creates and initializes an R4 instance.
|
|
2226
|
-
*/
|
|
2227
|
-
static async create(config) {
|
|
2228
|
-
const instance = new _R4(config);
|
|
2229
|
-
await instance.init();
|
|
2230
|
-
return instance;
|
|
2231
|
-
}
|
|
2232
|
-
/**
|
|
2233
|
-
* Initializes the SDK by registering the agent public key (idempotent) and
|
|
2234
|
-
* decrypting all accessible vault values locally into a flat env map.
|
|
2235
|
-
*/
|
|
2236
|
-
async init() {
|
|
2237
|
-
this._env = await this.fetchEnv();
|
|
2238
|
-
}
|
|
2239
|
-
/**
|
|
2240
|
-
* Returns the locally decrypted env map.
|
|
2241
|
-
*/
|
|
2242
|
-
get env() {
|
|
2243
|
-
if (!this._env) {
|
|
2244
|
-
throw new Error(
|
|
2245
|
-
"R4 SDK: env is not initialized. Call await r4.init() first, or use R4.create() for automatic initialization."
|
|
2246
|
-
);
|
|
2247
|
-
}
|
|
2248
|
-
return this._env;
|
|
2249
|
-
}
|
|
2250
|
-
/**
|
|
2251
|
-
* Re-fetches and locally re-decrypts the current vault view.
|
|
2252
|
-
*/
|
|
2253
|
-
async refresh() {
|
|
2254
|
-
this._env = await this.fetchEnv();
|
|
2255
|
-
}
|
|
2256
|
-
/**
|
|
2257
|
-
* Sends an authenticated request to an arbitrary machine API route so
|
|
2258
|
-
* callers can use the full headless surface without hand-rolling fetch code.
|
|
2259
|
-
*/
|
|
2260
|
-
async requestMachine(params) {
|
|
2261
|
-
return this.client.requestMachine(params);
|
|
2262
|
-
}
|
|
2263
|
-
/**
|
|
2264
|
-
* Fetches, verifies, and locally decrypts a token provider's vault-backed
|
|
2265
|
-
* credential fields without exposing plaintext back to R4.
|
|
2266
|
-
*/
|
|
2267
|
-
async getTokenProviderFields(tokenProviderId, options) {
|
|
2268
|
-
if (options?.unsafeExport !== true) {
|
|
2269
|
-
throw new Error(
|
|
2270
|
-
"R4 SDK: getTokenProviderFields exports decrypted provider secrets. Pass { unsafeExport: true } to confirm or use callAnthropicWithTokenProvider() instead."
|
|
2271
|
-
);
|
|
2272
|
-
}
|
|
2273
|
-
await this.registerAgentPublicKey();
|
|
2274
|
-
const runtime = await this.getVerifiedTokenProviderRuntime(tokenProviderId);
|
|
2275
|
-
return {
|
|
2276
|
-
id: runtime.id,
|
|
2277
|
-
name: runtime.name,
|
|
2278
|
-
providerType: runtime.providerType,
|
|
2279
|
-
fields: this.decryptTokenProviderFields(runtime)
|
|
2280
|
-
};
|
|
2281
|
-
}
|
|
2282
|
-
/**
|
|
2283
|
-
* Managed Anthropic call path:
|
|
2284
|
-
* 1. decrypt the provider secret locally
|
|
2285
|
-
* 2. preflight the budget gate with estimated usage
|
|
2286
|
-
* 3. call Anthropic directly
|
|
2287
|
-
* 4. publish finalized usage back to R4
|
|
2288
|
-
*/
|
|
2289
|
-
async callAnthropicWithTokenProvider(params) {
|
|
2290
|
-
await this.registerAgentPublicKey();
|
|
2291
|
-
const runtime = await this.getVerifiedTokenProviderRuntime(params.tokenProviderId);
|
|
2292
|
-
if (runtime.providerType !== "ANTHROPIC") {
|
|
2293
|
-
throw new Error(
|
|
2294
|
-
`R4 SDK: token provider ${params.tokenProviderId} is ${runtime.providerType}, not ANTHROPIC.`
|
|
2295
|
-
);
|
|
2296
|
-
}
|
|
2297
|
-
const fields = this.decryptTokenProviderFields(runtime);
|
|
2298
|
-
const apiKey = fields["API Key"];
|
|
2299
|
-
const endpoint = fields["Endpoint URL"] || "https://api.anthropic.com/v1/messages";
|
|
2300
|
-
const model = this.requireStringField(params.body.model, "body.model");
|
|
2301
|
-
if (!apiKey) {
|
|
2302
|
-
throw new Error(`R4 SDK: token provider ${params.tokenProviderId} does not contain an API Key field.`);
|
|
2303
|
-
}
|
|
2304
|
-
const requestId = randomUUID();
|
|
2305
|
-
const authorization = await this.client.authorizeTokenProviderUsage(params.tokenProviderId, {
|
|
2306
|
-
model,
|
|
2307
|
-
estimatedInputTokens: params.estimatedInputTokens ?? 0,
|
|
2308
|
-
estimatedOutputTokens: params.estimatedOutputTokens ?? this.inferEstimatedOutputTokens(params.body.max_tokens)
|
|
2309
|
-
});
|
|
2310
|
-
const response = await fetch(endpoint, {
|
|
2311
|
-
method: "POST",
|
|
2312
|
-
headers: {
|
|
2313
|
-
"Content-Type": "application/json",
|
|
2314
|
-
"x-api-key": apiKey,
|
|
2315
|
-
"anthropic-version": "2023-06-01"
|
|
2316
|
-
},
|
|
2317
|
-
body: JSON.stringify(params.body),
|
|
2318
|
-
signal: params.signal
|
|
2319
|
-
});
|
|
2320
|
-
if (!response.ok) {
|
|
2321
|
-
const bodyText = await response.text().catch(() => "");
|
|
2322
|
-
throw new Error(`Anthropic API error: ${response.status} ${response.statusText}${bodyText ? ` - ${bodyText}` : ""}`);
|
|
2323
|
-
}
|
|
2324
|
-
const parsedResponse = await response.json();
|
|
2325
|
-
const inputTokens = this.toNonNegativeInt(parsedResponse.usage?.input_tokens);
|
|
2326
|
-
const outputTokens = this.toNonNegativeInt(parsedResponse.usage?.output_tokens);
|
|
2327
|
-
const usage = await this.client.reportTokenProviderUsage(params.tokenProviderId, {
|
|
2328
|
-
requestId,
|
|
2329
|
-
model,
|
|
2330
|
-
vendorRequestId: typeof parsedResponse.id === "string" ? parsedResponse.id : null,
|
|
2331
|
-
inputTokens,
|
|
2332
|
-
outputTokens,
|
|
2333
|
-
totalTokens: inputTokens + outputTokens,
|
|
2334
|
-
estimatedCostUsd: authorization.estimatedCostUsd,
|
|
2335
|
-
metadata: {
|
|
2336
|
-
providerType: runtime.providerType
|
|
2337
|
-
}
|
|
2338
|
-
});
|
|
2339
|
-
return {
|
|
2340
|
-
response: parsedResponse,
|
|
2341
|
-
usage,
|
|
2342
|
-
authorization,
|
|
2343
|
-
requestId
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Registers the local agent public key, loads all accessible vaults, verifies
|
|
2348
|
-
* wrapped-DEK signatures against pinned signer keys, unwraps each vault DEK,
|
|
2349
|
-
* and builds a flat SCREAMING_SNAKE_CASE env map from decrypted field values.
|
|
2350
|
-
*/
|
|
2351
|
-
async fetchEnv() {
|
|
2352
|
-
await this.registerAgentPublicKey();
|
|
2353
|
-
const { vaults } = await this.client.listVaults(this.projectId);
|
|
2354
|
-
const envEntries = await Promise.all(vaults.map((vault) => this.fetchVaultEnv(vault.id)));
|
|
2355
|
-
return Object.assign({}, ...envEntries);
|
|
2356
|
-
}
|
|
2357
|
-
/**
|
|
2358
|
-
* Ensures the local agent public key is registered before any zero-trust
|
|
2359
|
-
* wrapped-key or token-provider flow runs.
|
|
2360
|
-
*/
|
|
2361
|
-
async registerAgentPublicKey() {
|
|
2362
|
-
try {
|
|
2363
|
-
await this.client.registerAgentPublicKey({
|
|
2364
|
-
publicKey: this.publicKeyPem
|
|
2365
|
-
});
|
|
2366
|
-
} catch (error) {
|
|
2367
|
-
throw new Error(
|
|
2368
|
-
`R4 SDK: failed to register the local agent public key. The zero-trust SDK requires an AGENT-scoped API key and a matching local private key. ${error instanceof Error ? error.message : String(error)}`
|
|
2369
|
-
);
|
|
2370
|
-
}
|
|
2371
|
-
}
|
|
2372
|
-
/**
|
|
2373
|
-
* Fetches a single vault's wrapped DEK, verifies it against the pinned signer
|
|
2374
|
-
* directory, unwraps the DEK locally, then decrypts every field value in that
|
|
2375
|
-
* vault item-by-item.
|
|
2376
|
-
*/
|
|
2377
|
-
async fetchVaultEnv(vaultId) {
|
|
2378
|
-
const pinnedTransparencyHead = getSinglePinnedTransparencyHead(this.trustStorePath);
|
|
2379
|
-
const [wrappedKey, itemsResponse, initialPublicKeyDirectory] = await Promise.all([
|
|
2380
|
-
this.client.getAgentWrappedKey(vaultId),
|
|
2381
|
-
this.client.listVaultItems(vaultId),
|
|
2382
|
-
this.client.getVaultUserKeyDirectory(
|
|
2383
|
-
vaultId,
|
|
2384
|
-
pinnedTransparencyHead ? {
|
|
2385
|
-
knownTransparencyVersion: pinnedTransparencyHead.version,
|
|
2386
|
-
knownTransparencyHash: pinnedTransparencyHead.hash
|
|
2387
|
-
} : void 0
|
|
2388
|
-
)
|
|
2389
|
-
]);
|
|
2390
|
-
let publicKeyDirectory = initialPublicKeyDirectory;
|
|
2391
|
-
let witnessAnchorHead = null;
|
|
2392
|
-
if (!pinnedTransparencyHead) {
|
|
2393
|
-
const orgId = initialPublicKeyDirectory.directoryCheckpoint?.checkpoint.orgId ?? null;
|
|
2394
|
-
if (orgId && initialPublicKeyDirectory.directoryCheckpoint && initialPublicKeyDirectory.transparency) {
|
|
2395
|
-
witnessAnchorHead = await getPublicOrgWitnessHead(
|
|
2396
|
-
this.baseUrl,
|
|
2397
|
-
orgId,
|
|
2398
|
-
this.transparencyWitnessUrl
|
|
2399
|
-
);
|
|
2400
|
-
if (witnessAnchorHead) {
|
|
2401
|
-
if (initialPublicKeyDirectory.transparency.head.version < witnessAnchorHead.version) {
|
|
2402
|
-
throw new Error(`R4 SDK: public transparency witness head is ahead of the server response for org ${orgId}.`);
|
|
2403
|
-
}
|
|
2404
|
-
if (initialPublicKeyDirectory.transparency.head.version === witnessAnchorHead.version) {
|
|
2405
|
-
if (initialPublicKeyDirectory.transparency.head.hash !== witnessAnchorHead.hash) {
|
|
2406
|
-
throw new Error(`R4 SDK: public transparency witness head fork detected for org ${orgId}.`);
|
|
2407
|
-
}
|
|
2408
|
-
} else {
|
|
2409
|
-
publicKeyDirectory = await this.client.getVaultUserKeyDirectory(vaultId, {
|
|
2410
|
-
knownTransparencyVersion: witnessAnchorHead.version,
|
|
2411
|
-
knownTransparencyHash: witnessAnchorHead.hash
|
|
2412
|
-
});
|
|
2413
|
-
}
|
|
910
|
+
return new Command("create").description("Create a new agent").requiredOption("--name <name>", "Agent name").requiredOption("--domain-tenant-id <id>", "Target domain tenant ID").option("--budget-id <id>", "Existing budget ID to assign").option("--vault-id <id>", "Optional vault ID to store credentials in").option("--new-budget-name <name>", "Create and assign a new per-agent budget").option("--new-budget-description <text>", "Description for the new per-agent budget").option("--new-budget-target <amount>", "Target amount for the new per-agent budget").option(
|
|
911
|
+
"--security-group-id <id>",
|
|
912
|
+
"Security group IDs (repeat or use comma-separated values)",
|
|
913
|
+
collectOptionValues,
|
|
914
|
+
[]
|
|
915
|
+
).option(
|
|
916
|
+
"--permission <grant>",
|
|
917
|
+
"Machine permission grant (repeat or use comma-separated values)",
|
|
918
|
+
collectOptionValues,
|
|
919
|
+
[]
|
|
920
|
+
).action(
|
|
921
|
+
withErrorHandler(
|
|
922
|
+
async (options, cmd) => {
|
|
923
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
924
|
+
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
925
|
+
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
926
|
+
const body = buildCreateAgentBody(options);
|
|
927
|
+
const spinner = ora("Creating agent...").start();
|
|
928
|
+
const response = await client.createAgent(body);
|
|
929
|
+
spinner.stop();
|
|
930
|
+
if (globalOpts.json) {
|
|
931
|
+
console.log(JSON.stringify(response, null, 2));
|
|
932
|
+
return;
|
|
2414
933
|
}
|
|
934
|
+
success(`Created agent "${response.name}"`);
|
|
935
|
+
printDetail(
|
|
936
|
+
[
|
|
937
|
+
["ID", response.id],
|
|
938
|
+
["Access Key", response.accessKey],
|
|
939
|
+
["Access Secret", response.accessSecret],
|
|
940
|
+
["Vault Item", response.vaultItemId]
|
|
941
|
+
],
|
|
942
|
+
false
|
|
943
|
+
);
|
|
2415
944
|
}
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
)?.publicKey ?? historicalAgentSignerKey?.publicKey : signerKey?.publicKey;
|
|
2437
|
-
if (!wrappedKeySignerPublicKey) {
|
|
2438
|
-
throw new Error(
|
|
2439
|
-
`R4 SDK: wrapped DEK signer ${wrappedKey.signerEncryptionKeyId ?? wrappedKey.signerUserKeyPairId ?? "unknown"} was not present in the trusted key directory.`
|
|
2440
|
-
);
|
|
2441
|
-
}
|
|
2442
|
-
const signatureVerified = verifyWrappedDekSignature(
|
|
2443
|
-
vaultId,
|
|
2444
|
-
wrappedKey.encryptionKeyId,
|
|
2445
|
-
wrappedKey.signerEncryptionKeyId ?? wrappedKey.signerUserKeyPairId,
|
|
2446
|
-
wrappedKey.dekVersion,
|
|
2447
|
-
wrappedKey.wrappedDek,
|
|
2448
|
-
wrappedKey.wrappedDekSignature,
|
|
2449
|
-
wrappedKeySignerPublicKey
|
|
2450
|
-
);
|
|
2451
|
-
if (!signatureVerified) {
|
|
2452
|
-
throw new Error(`R4 SDK: wrapped DEK signature verification failed for vault ${vaultId}.`);
|
|
2453
|
-
}
|
|
2454
|
-
const dek = unwrapDEKWithPrivateKey(wrappedKey.wrappedDek, this.privateKeyPem);
|
|
2455
|
-
if (!itemsResponse.summaryCheckpoint) {
|
|
2456
|
-
throw new Error(`R4 SDK: vault ${vaultId} is missing a signed summary checkpoint.`);
|
|
2457
|
-
}
|
|
2458
|
-
const expectedSummaryCheckpoint = buildVaultSummaryCheckpointFromListResponse(
|
|
2459
|
-
itemsResponse,
|
|
2460
|
-
itemsResponse.summaryCheckpoint.checkpoint.version
|
|
2461
|
-
);
|
|
2462
|
-
const summaryVerified = verifyVaultSummaryCheckpoint(
|
|
2463
|
-
expectedSummaryCheckpoint,
|
|
2464
|
-
itemsResponse.summaryCheckpoint.signature,
|
|
2465
|
-
getCheckpointSignerPublicKey({
|
|
2466
|
-
checkpoint: itemsResponse.summaryCheckpoint,
|
|
2467
|
-
userKeys: trustedPublicKeys,
|
|
2468
|
-
agentKeys: trustedAgentPublicKeys
|
|
2469
|
-
})
|
|
2470
|
-
);
|
|
2471
|
-
if (!summaryVerified) {
|
|
2472
|
-
throw new Error(`R4 SDK: vault summary checkpoint verification failed for vault ${vaultId}.`);
|
|
2473
|
-
}
|
|
2474
|
-
assertAndPinCheckpointVersion(
|
|
2475
|
-
this.trustStorePath,
|
|
2476
|
-
`summary:${vaultId}`,
|
|
2477
|
-
expectedSummaryCheckpoint.version
|
|
2478
|
-
);
|
|
2479
|
-
const itemDetails = await Promise.all(
|
|
2480
|
-
itemsResponse.items.map((item) => this.client.getVaultItemDetail(vaultId, item.id))
|
|
2481
|
-
);
|
|
2482
|
-
const env = {};
|
|
2483
|
-
for (const item of itemDetails) {
|
|
2484
|
-
if (!item.detailCheckpoint) {
|
|
2485
|
-
throw new Error(`R4 SDK: vault item ${item.id} is missing a signed detail checkpoint.`);
|
|
2486
|
-
}
|
|
2487
|
-
const expectedDetailCheckpoint = buildVaultItemDetailCheckpointFromResponse(
|
|
2488
|
-
item,
|
|
2489
|
-
item.detailCheckpoint.checkpoint.version
|
|
2490
|
-
);
|
|
2491
|
-
const detailVerified = verifyVaultItemDetailCheckpoint(
|
|
2492
|
-
expectedDetailCheckpoint,
|
|
2493
|
-
item.detailCheckpoint.signature,
|
|
2494
|
-
getCheckpointSignerPublicKey({
|
|
2495
|
-
checkpoint: item.detailCheckpoint,
|
|
2496
|
-
userKeys: trustedPublicKeys,
|
|
2497
|
-
agentKeys: trustedAgentPublicKeys
|
|
2498
|
-
})
|
|
2499
|
-
);
|
|
2500
|
-
if (!detailVerified) {
|
|
2501
|
-
throw new Error(`R4 SDK: vault item checkpoint verification failed for item ${item.id}.`);
|
|
945
|
+
)
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// src/commands/agent/get.ts
|
|
950
|
+
import { Command as Command2 } from "commander";
|
|
951
|
+
import chalk2 from "chalk";
|
|
952
|
+
import ora2 from "ora";
|
|
953
|
+
function getCommand() {
|
|
954
|
+
return new Command2("get").description("Get agent details").argument("<id>", "Agent ID").action(
|
|
955
|
+
withErrorHandler(async (id, _opts, cmd) => {
|
|
956
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
957
|
+
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
958
|
+
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
959
|
+
const spinner = ora2("Fetching agent...").start();
|
|
960
|
+
const agent = await client.getAgent(id);
|
|
961
|
+
spinner.stop();
|
|
962
|
+
if (globalOpts.json) {
|
|
963
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
964
|
+
return;
|
|
2502
965
|
}
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
966
|
+
console.log();
|
|
967
|
+
console.log(chalk2.bold(` ${agent.name}`));
|
|
968
|
+
console.log();
|
|
969
|
+
printDetail(
|
|
970
|
+
[
|
|
971
|
+
["ID", agent.id],
|
|
972
|
+
["Tenant", agent.tenantName],
|
|
973
|
+
["Domain", agent.domainName],
|
|
974
|
+
["Domain Tenant ID", agent.domainTenantId],
|
|
975
|
+
["Budget", agent.budgetId],
|
|
976
|
+
["Public Key Ready", agent.isPublicKeyRegistered ? "Yes" : "No"],
|
|
977
|
+
["Created At", agent.createdAt],
|
|
978
|
+
["Updated At", agent.updatedAt],
|
|
979
|
+
["Archived At", agent.archivedAt]
|
|
980
|
+
],
|
|
981
|
+
false
|
|
2507
982
|
);
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
}
|
|
2517
|
-
}
|
|
2518
|
-
return env;
|
|
2519
|
-
}
|
|
2520
|
-
async getVerifiedTokenProviderRuntime(tokenProviderId) {
|
|
2521
|
-
const pinnedTransparencyHead = getSinglePinnedTransparencyHead(this.trustStorePath);
|
|
2522
|
-
let runtime = await this.client.getTokenProviderRuntime(
|
|
2523
|
-
tokenProviderId,
|
|
2524
|
-
pinnedTransparencyHead ? {
|
|
2525
|
-
knownTransparencyVersion: pinnedTransparencyHead.version,
|
|
2526
|
-
knownTransparencyHash: pinnedTransparencyHead.hash
|
|
2527
|
-
} : void 0
|
|
2528
|
-
);
|
|
2529
|
-
let witnessAnchorHead = null;
|
|
2530
|
-
if (!pinnedTransparencyHead) {
|
|
2531
|
-
const orgId = runtime.keyDirectory.directoryCheckpoint?.checkpoint.orgId ?? null;
|
|
2532
|
-
if (orgId && runtime.keyDirectory.directoryCheckpoint && runtime.keyDirectory.transparency) {
|
|
2533
|
-
witnessAnchorHead = await getPublicOrgWitnessHead(
|
|
2534
|
-
this.baseUrl,
|
|
2535
|
-
orgId,
|
|
2536
|
-
this.transparencyWitnessUrl
|
|
983
|
+
if (agent.securityGroups.length > 0) {
|
|
984
|
+
console.log();
|
|
985
|
+
console.log(chalk2.bold(" Security Groups"));
|
|
986
|
+
console.log();
|
|
987
|
+
printTable(
|
|
988
|
+
["ID", "Name"],
|
|
989
|
+
agent.securityGroups.map((group) => [group.id, group.name]),
|
|
990
|
+
false
|
|
2537
991
|
);
|
|
2538
|
-
if (witnessAnchorHead) {
|
|
2539
|
-
if (runtime.keyDirectory.transparency.head.version < witnessAnchorHead.version) {
|
|
2540
|
-
throw new Error(
|
|
2541
|
-
`R4 SDK: public transparency witness head is ahead of the server response for org ${orgId}.`
|
|
2542
|
-
);
|
|
2543
|
-
}
|
|
2544
|
-
if (runtime.keyDirectory.transparency.head.version === witnessAnchorHead.version) {
|
|
2545
|
-
if (runtime.keyDirectory.transparency.head.hash !== witnessAnchorHead.hash) {
|
|
2546
|
-
throw new Error(`R4 SDK: public transparency witness head fork detected for org ${orgId}.`);
|
|
2547
|
-
}
|
|
2548
|
-
} else {
|
|
2549
|
-
runtime = await this.client.getTokenProviderRuntime(tokenProviderId, {
|
|
2550
|
-
knownTransparencyVersion: witnessAnchorHead.version,
|
|
2551
|
-
knownTransparencyHash: witnessAnchorHead.hash
|
|
2552
|
-
});
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
992
|
}
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
runtime.vaultId,
|
|
2577
|
-
runtime.wrappedKey.encryptionKeyId,
|
|
2578
|
-
runtime.wrappedKey.signerEncryptionKeyId ?? runtime.wrappedKey.signerUserKeyPairId,
|
|
2579
|
-
runtime.wrappedKey.dekVersion,
|
|
2580
|
-
runtime.wrappedKey.wrappedDek,
|
|
2581
|
-
runtime.wrappedKey.wrappedDekSignature,
|
|
2582
|
-
wrappedKeySignerPublicKey
|
|
2583
|
-
)) {
|
|
2584
|
-
throw new Error(`R4 SDK: wrapped DEK signature verification failed for vault ${runtime.vaultId}.`);
|
|
2585
|
-
}
|
|
2586
|
-
const expectedDetailCheckpoint = buildVaultItemDetailCheckpointFromResponse(
|
|
2587
|
-
runtime.vaultItemDetail,
|
|
2588
|
-
runtime.vaultItemDetail.detailCheckpoint?.checkpoint.version ?? 0
|
|
2589
|
-
);
|
|
2590
|
-
if (!runtime.vaultItemDetail.detailCheckpoint) {
|
|
2591
|
-
throw new Error(`R4 SDK: vault item ${runtime.vaultItemDetail.id} is missing a signed detail checkpoint.`);
|
|
2592
|
-
}
|
|
2593
|
-
const detailVerified = verifyVaultItemDetailCheckpoint(
|
|
2594
|
-
expectedDetailCheckpoint,
|
|
2595
|
-
runtime.vaultItemDetail.detailCheckpoint.signature,
|
|
2596
|
-
getCheckpointSignerPublicKey({
|
|
2597
|
-
checkpoint: runtime.vaultItemDetail.detailCheckpoint,
|
|
2598
|
-
userKeys: trustedPublicKeys,
|
|
2599
|
-
agentKeys: trustedAgentPublicKeys
|
|
2600
|
-
})
|
|
2601
|
-
);
|
|
2602
|
-
if (!detailVerified) {
|
|
2603
|
-
throw new Error(
|
|
2604
|
-
`R4 SDK: vault item checkpoint verification failed for item ${runtime.vaultItemDetail.id}.`
|
|
2605
|
-
);
|
|
2606
|
-
}
|
|
2607
|
-
assertAndPinCheckpointVersion(
|
|
2608
|
-
this.trustStorePath,
|
|
2609
|
-
`detail:${runtime.vaultItemDetail.id}`,
|
|
2610
|
-
expectedDetailCheckpoint.version
|
|
2611
|
-
);
|
|
2612
|
-
return runtime;
|
|
2613
|
-
}
|
|
2614
|
-
decryptTokenProviderFields(runtime) {
|
|
2615
|
-
const dek = unwrapDEKWithPrivateKey(runtime.wrappedKey.wrappedDek, this.privateKeyPem);
|
|
2616
|
-
const fields = {};
|
|
2617
|
-
for (const field of runtime.vaultItemDetail.fields) {
|
|
2618
|
-
if (field.value === null) {
|
|
2619
|
-
continue;
|
|
993
|
+
console.log();
|
|
994
|
+
})
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/commands/agent/get-tenant-roles.ts
|
|
999
|
+
import { Command as Command3 } from "commander";
|
|
1000
|
+
import ora3 from "ora";
|
|
1001
|
+
function getTenantRolesCommand() {
|
|
1002
|
+
return new Command3("get-tenant-roles").description("Get the tenant roles assigned to an agent").argument("<id>", "Agent ID").action(
|
|
1003
|
+
withErrorHandler(async (id, _opts, cmd) => {
|
|
1004
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
1005
|
+
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
1006
|
+
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
1007
|
+
const spinner = ora3("Fetching agent tenant roles...").start();
|
|
1008
|
+
const response = await client.getAgentTenantRoles(id);
|
|
1009
|
+
spinner.stop();
|
|
1010
|
+
if (globalOpts.json) {
|
|
1011
|
+
console.log(JSON.stringify(response, null, 2));
|
|
1012
|
+
return;
|
|
2620
1013
|
}
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
const historicalAgentSignerKey = agentKeys.flatMap((publicKey) => publicKey.transparency?.entries ?? []).find((entry) => entry.encryptionKeyId === wrappedKey.signerEncryptionKeyId);
|
|
2630
|
-
const wrappedKeySignerPublicKey = wrappedKey.signerEncryptionKeyId ? agentKeys.find(
|
|
2631
|
-
(publicKey) => publicKey.encryptionKeyId === wrappedKey.signerEncryptionKeyId
|
|
2632
|
-
)?.publicKey ?? historicalAgentSignerKey?.publicKey : signerKey?.publicKey;
|
|
2633
|
-
if (!wrappedKeySignerPublicKey) {
|
|
2634
|
-
throw new Error(
|
|
2635
|
-
`R4 SDK: wrapped DEK signer ${wrappedKey.signerEncryptionKeyId ?? wrappedKey.signerUserKeyPairId ?? "unknown"} was not present in the trusted key directory.`
|
|
1014
|
+
console.log();
|
|
1015
|
+
printDetail(
|
|
1016
|
+
[
|
|
1017
|
+
["Agent ID", id],
|
|
1018
|
+
["Explicit Roles", response.roles.join(", ") || "(none)"],
|
|
1019
|
+
["Inherited Roles", response.inheritedRoles.join(", ") || "(none)"]
|
|
1020
|
+
],
|
|
1021
|
+
false
|
|
2636
1022
|
);
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
return 0;
|
|
2651
|
-
}
|
|
2652
|
-
toNonNegativeInt(value) {
|
|
2653
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
2654
|
-
return Math.floor(value);
|
|
2655
|
-
}
|
|
2656
|
-
return 0;
|
|
2657
|
-
}
|
|
2658
|
-
};
|
|
2659
|
-
var src_default = R4;
|
|
1023
|
+
console.log();
|
|
1024
|
+
})
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
// src/commands/agent/init.ts
|
|
1029
|
+
import { Command as Command5 } from "commander";
|
|
1030
|
+
import ora5 from "ora";
|
|
1031
|
+
|
|
1032
|
+
// src/commands/doctor.ts
|
|
1033
|
+
import { Command as Command4 } from "commander";
|
|
1034
|
+
import chalk3 from "chalk";
|
|
1035
|
+
import ora4 from "ora";
|
|
2660
1036
|
|
|
2661
1037
|
// src/lib/doctor.ts
|
|
1038
|
+
import R4 from "@r4-sdk/node";
|
|
2662
1039
|
function getAgentIdFromRegistration(registration) {
|
|
2663
1040
|
const entryCount = registration?.transparency?.entries.length ?? 0;
|
|
2664
1041
|
if (!registration?.transparency || entryCount === 0) {
|
|
@@ -2932,7 +1309,7 @@ async function runDoctorChecks(connection) {
|
|
|
2932
1309
|
return report;
|
|
2933
1310
|
}
|
|
2934
1311
|
try {
|
|
2935
|
-
const r4 = await
|
|
1312
|
+
const r4 = await R4.create({
|
|
2936
1313
|
apiKey: connection.apiKey,
|
|
2937
1314
|
baseUrl: connection.baseUrl,
|
|
2938
1315
|
dev: connection.dev,
|
|
@@ -3048,8 +1425,8 @@ function doctorCommand(commandName = "doctor", description = "Verify CLI auth, r
|
|
|
3048
1425
|
}
|
|
3049
1426
|
|
|
3050
1427
|
// src/lib/credentials-file.ts
|
|
3051
|
-
import
|
|
3052
|
-
import
|
|
1428
|
+
import fs5 from "fs";
|
|
1429
|
+
import path3 from "path";
|
|
3053
1430
|
function normalizeFieldName(fieldName) {
|
|
3054
1431
|
return fieldName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3055
1432
|
}
|
|
@@ -3255,9 +1632,9 @@ function parsePlainTextContent(content) {
|
|
|
3255
1632
|
return {};
|
|
3256
1633
|
}
|
|
3257
1634
|
function parseCredentialsFile(credentialsFilePath) {
|
|
3258
|
-
const resolvedPath =
|
|
3259
|
-
const content =
|
|
3260
|
-
const extension =
|
|
1635
|
+
const resolvedPath = path3.resolve(credentialsFilePath);
|
|
1636
|
+
const content = fs5.readFileSync(resolvedPath, "utf8");
|
|
1637
|
+
const extension = path3.extname(resolvedPath).toLowerCase();
|
|
3261
1638
|
if (extension === ".json") {
|
|
3262
1639
|
return parseJsonContent(content);
|
|
3263
1640
|
}
|
|
@@ -3380,8 +1757,8 @@ function cacheProfileIdentity(profileName, identity) {
|
|
|
3380
1757
|
}
|
|
3381
1758
|
|
|
3382
1759
|
// src/lib/prompt.ts
|
|
3383
|
-
import readline from "
|
|
3384
|
-
import { Writable } from "
|
|
1760
|
+
import readline from "readline";
|
|
1761
|
+
import { Writable } from "stream";
|
|
3385
1762
|
var MutableOutput = class extends Writable {
|
|
3386
1763
|
muted = false;
|
|
3387
1764
|
_write(chunk, _encoding, callback) {
|
|
@@ -3860,14 +2237,14 @@ function logoutCommand() {
|
|
|
3860
2237
|
import { Command as Command11 } from "commander";
|
|
3861
2238
|
|
|
3862
2239
|
// src/lib/public-auth-client.ts
|
|
3863
|
-
import
|
|
2240
|
+
import crypto2 from "crypto";
|
|
3864
2241
|
var PublicAuthClient = class {
|
|
3865
2242
|
baseUrl;
|
|
3866
2243
|
constructor(baseUrl) {
|
|
3867
2244
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
3868
2245
|
}
|
|
3869
|
-
async request(
|
|
3870
|
-
const response = await fetch(`${this.baseUrl}${
|
|
2246
|
+
async request(path6, body) {
|
|
2247
|
+
const response = await fetch(`${this.baseUrl}${path6}`, {
|
|
3871
2248
|
method: "POST",
|
|
3872
2249
|
headers: {
|
|
3873
2250
|
"Content-Type": "application/json",
|
|
@@ -3893,7 +2270,7 @@ var PublicAuthClient = class {
|
|
|
3893
2270
|
}
|
|
3894
2271
|
};
|
|
3895
2272
|
function solveAgentChallenge(nonce) {
|
|
3896
|
-
return
|
|
2273
|
+
return crypto2.createHash("sha256").update(nonce).digest("hex");
|
|
3897
2274
|
}
|
|
3898
2275
|
|
|
3899
2276
|
// src/lib/register-agent.ts
|
|
@@ -4273,8 +2650,8 @@ import { Command as Command16 } from "commander";
|
|
|
4273
2650
|
import ora10 from "ora";
|
|
4274
2651
|
|
|
4275
2652
|
// src/lib/json-input.ts
|
|
4276
|
-
import
|
|
4277
|
-
import
|
|
2653
|
+
import fs6 from "fs";
|
|
2654
|
+
import path4 from "path";
|
|
4278
2655
|
function parseJsonInput(params) {
|
|
4279
2656
|
const bodyFlagName = params.bodyFlagName ?? "--body";
|
|
4280
2657
|
const bodyFileFlagName = params.bodyFileFlagName ?? "--body-file";
|
|
@@ -4284,7 +2661,7 @@ function parseJsonInput(params) {
|
|
|
4284
2661
|
if (!params.body && !params.bodyFile) {
|
|
4285
2662
|
return void 0;
|
|
4286
2663
|
}
|
|
4287
|
-
const rawInput = params.bodyFile ?
|
|
2664
|
+
const rawInput = params.bodyFile ? fs6.readFileSync(path4.resolve(params.bodyFile), "utf8") : params.body;
|
|
4288
2665
|
const sourceLabel = params.bodyFile ? `${bodyFileFlagName} ${params.bodyFile}` : bodyFlagName;
|
|
4289
2666
|
try {
|
|
4290
2667
|
return JSON.parse(rawInput);
|
|
@@ -4385,7 +2762,7 @@ function registerBudgetCommands(program2) {
|
|
|
4385
2762
|
|
|
4386
2763
|
// src/commands/configure.ts
|
|
4387
2764
|
import { Command as Command18 } from "commander";
|
|
4388
|
-
import
|
|
2765
|
+
import fs7 from "fs";
|
|
4389
2766
|
async function promptRuntimeTarget(existingProfile) {
|
|
4390
2767
|
const defaultTarget = existingProfile.baseUrl ? "custom" : existingProfile.dev ? "dev" : "prod";
|
|
4391
2768
|
const runtimeTarget = await promptChoice(
|
|
@@ -4418,7 +2795,7 @@ async function promptRuntimeTarget(existingProfile) {
|
|
|
4418
2795
|
}
|
|
4419
2796
|
async function resolvePrivateKeyMaterial(profileName, existingProfile) {
|
|
4420
2797
|
const managedPrivateKeyPath = getDefaultPrivateKeyPath(profileName);
|
|
4421
|
-
const defaultMode = existingProfile.privateKeyPath ||
|
|
2798
|
+
const defaultMode = existingProfile.privateKeyPath || fs7.existsSync(managedPrivateKeyPath) ? "existing" : "generate";
|
|
4422
2799
|
const privateKeyMode = await promptChoice(
|
|
4423
2800
|
"How should this profile handle its local private key?",
|
|
4424
2801
|
[
|
|
@@ -4721,13 +3098,13 @@ function normalizeMethod(method) {
|
|
|
4721
3098
|
function requestCommand() {
|
|
4722
3099
|
return new Command23("request").description("Call an arbitrary machine API route").argument("<method>", "HTTP method (GET, POST, PUT, PATCH, DELETE)").argument("<path>", "Machine path like /webhook or /api/v1/machine/webhook").option("--body <json>", "Optional JSON request body").option("--body-file <path>", "Read the JSON request body from a file").action(
|
|
4723
3100
|
withErrorHandler(
|
|
4724
|
-
async (method,
|
|
3101
|
+
async (method, path6, options, cmd) => {
|
|
4725
3102
|
const globalOpts = cmd.optsWithGlobals();
|
|
4726
3103
|
const config = resolveAuth(globalOpts);
|
|
4727
3104
|
const client = new CliClient(config.apiKey, config.baseUrl);
|
|
4728
3105
|
const result = await client.requestMachine({
|
|
4729
3106
|
method: normalizeMethod(method),
|
|
4730
|
-
path:
|
|
3107
|
+
path: path6,
|
|
4731
3108
|
body: parseJsonInput({
|
|
4732
3109
|
body: options.body,
|
|
4733
3110
|
bodyFile: options.bodyFile
|
|
@@ -5032,17 +3409,64 @@ function createItemCommand() {
|
|
|
5032
3409
|
);
|
|
5033
3410
|
}
|
|
5034
3411
|
|
|
5035
|
-
// src/commands/vault/
|
|
3412
|
+
// src/commands/vault/download-asset.ts
|
|
3413
|
+
import path5 from "path";
|
|
5036
3414
|
import { Command as Command32 } from "commander";
|
|
5037
3415
|
import ora21 from "ora";
|
|
5038
|
-
import R42 from "@r4-sdk/
|
|
3416
|
+
import R42 from "@r4-sdk/node";
|
|
3417
|
+
function downloadAssetCommand() {
|
|
3418
|
+
return new Command32("download-asset").description("Download and locally decrypt a vault attachment by vault ID and asset ID").argument("<vaultId>", "Vault ID that owns the attachment").argument("<assetId>", "Attachment asset ID to download").option(
|
|
3419
|
+
"--output <path>",
|
|
3420
|
+
"Destination file path (defaults to ./${assetId}.bin when omitted)"
|
|
3421
|
+
).action(
|
|
3422
|
+
withErrorHandler(
|
|
3423
|
+
async (vaultId, assetId, opts, cmd) => {
|
|
3424
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
3425
|
+
const config = resolveAuth(globalOpts);
|
|
3426
|
+
const sdk = new R42(config);
|
|
3427
|
+
const outputPath = path5.resolve(opts.output ?? `${assetId}.bin`);
|
|
3428
|
+
const spinner = ora21("Downloading and decrypting attachment...").start();
|
|
3429
|
+
const result = await sdk.downloadVaultAttachment({
|
|
3430
|
+
vaultId,
|
|
3431
|
+
assetId,
|
|
3432
|
+
outputPath
|
|
3433
|
+
});
|
|
3434
|
+
spinner.stop();
|
|
3435
|
+
if (globalOpts.json) {
|
|
3436
|
+
console.log(
|
|
3437
|
+
JSON.stringify(
|
|
3438
|
+
{
|
|
3439
|
+
assetId: result.assetId,
|
|
3440
|
+
vaultId: result.vaultId,
|
|
3441
|
+
outputPath: result.outputPath,
|
|
3442
|
+
plaintextSize: result.checkpoint.plaintextSize,
|
|
3443
|
+
plaintextSha256: result.checkpoint.plaintextSha256,
|
|
3444
|
+
ciphertextSize: result.checkpoint.ciphertextSize,
|
|
3445
|
+
ciphertextSha256: result.checkpoint.ciphertextSha256
|
|
3446
|
+
},
|
|
3447
|
+
null,
|
|
3448
|
+
2
|
|
3449
|
+
)
|
|
3450
|
+
);
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
success(`Saved decrypted attachment to ${outputPath}`);
|
|
3454
|
+
}
|
|
3455
|
+
)
|
|
3456
|
+
);
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
// src/commands/vault/list.ts
|
|
3460
|
+
import { Command as Command33 } from "commander";
|
|
3461
|
+
import ora22 from "ora";
|
|
3462
|
+
import R43 from "@r4-sdk/node";
|
|
5039
3463
|
function listCommand5() {
|
|
5040
|
-
return new
|
|
3464
|
+
return new Command33("list").description("List all locally decrypted environment variables").action(
|
|
5041
3465
|
withErrorHandler(async (_opts, cmd) => {
|
|
5042
3466
|
const globalOpts = cmd.optsWithGlobals();
|
|
5043
3467
|
const config = resolveAuth(globalOpts);
|
|
5044
|
-
const spinner =
|
|
5045
|
-
const r4 = await
|
|
3468
|
+
const spinner = ora22("Fetching environment variables...").start();
|
|
3469
|
+
const r4 = await R43.create(config);
|
|
5046
3470
|
spinner.stop();
|
|
5047
3471
|
const env = r4.env;
|
|
5048
3472
|
const keys = Object.keys(env).sort();
|
|
@@ -5063,12 +3487,13 @@ function listCommand5() {
|
|
|
5063
3487
|
}
|
|
5064
3488
|
|
|
5065
3489
|
// src/commands/vault/list-items.ts
|
|
5066
|
-
import { Command as
|
|
3490
|
+
import { Command as Command35 } from "commander";
|
|
5067
3491
|
|
|
5068
3492
|
// src/commands/vault/items.ts
|
|
5069
|
-
import { Command as
|
|
5070
|
-
import
|
|
5071
|
-
import
|
|
3493
|
+
import { Command as Command34 } from "commander";
|
|
3494
|
+
import ora23 from "ora";
|
|
3495
|
+
import R44 from "@r4-sdk/node";
|
|
3496
|
+
var DIRECT_ITEM_SHARE_VAULT_LABEL = "[Direct Item Share]";
|
|
5072
3497
|
function deriveItems(env) {
|
|
5073
3498
|
const keys = Object.keys(env).sort();
|
|
5074
3499
|
return keys.map((key) => ({
|
|
@@ -5079,15 +3504,18 @@ function deriveItems(env) {
|
|
|
5079
3504
|
async function loadVaultMetadataRows(globalOpts) {
|
|
5080
3505
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5081
3506
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5082
|
-
const { vaults } = await
|
|
5083
|
-
|
|
3507
|
+
const [{ vaults }, sharedItemsResponse] = await Promise.all([
|
|
3508
|
+
client.listVaults(connection.projectId),
|
|
3509
|
+
connection.projectId ? Promise.resolve(null) : client.getSharedVaultItems()
|
|
3510
|
+
]);
|
|
3511
|
+
const rowsByItemId = /* @__PURE__ */ new Map();
|
|
5084
3512
|
for (const vault of vaults) {
|
|
5085
3513
|
const response = await client.listVaultItems(vault.id);
|
|
5086
3514
|
const groupNames = Object.fromEntries(
|
|
5087
3515
|
response.vaultItemGroups.map((group) => [group.id, group.name])
|
|
5088
3516
|
);
|
|
5089
3517
|
for (const item of response.items) {
|
|
5090
|
-
|
|
3518
|
+
rowsByItemId.set(item.id, {
|
|
5091
3519
|
vaultId: vault.id,
|
|
5092
3520
|
vaultName: vault.name,
|
|
5093
3521
|
itemId: item.id,
|
|
@@ -5099,13 +3527,33 @@ async function loadVaultMetadataRows(globalOpts) {
|
|
|
5099
3527
|
});
|
|
5100
3528
|
}
|
|
5101
3529
|
}
|
|
5102
|
-
|
|
3530
|
+
if (sharedItemsResponse) {
|
|
3531
|
+
const groupNames = Object.fromEntries(
|
|
3532
|
+
sharedItemsResponse.vaultItemGroups.map((group) => [group.id, group.name])
|
|
3533
|
+
);
|
|
3534
|
+
for (const item of sharedItemsResponse.vaultItems) {
|
|
3535
|
+
if (rowsByItemId.has(item.id)) {
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
rowsByItemId.set(item.id, {
|
|
3539
|
+
vaultId: item.vaultId,
|
|
3540
|
+
vaultName: DIRECT_ITEM_SHARE_VAULT_LABEL,
|
|
3541
|
+
itemId: item.id,
|
|
3542
|
+
itemName: item.name,
|
|
3543
|
+
type: item.type,
|
|
3544
|
+
fieldCount: item.fieldCount ?? null,
|
|
3545
|
+
groupName: item.groupId ? groupNames[item.groupId] ?? item.groupId : null,
|
|
3546
|
+
websites: item.websites
|
|
3547
|
+
});
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return [...rowsByItemId.values()].sort((left, right) => {
|
|
5103
3551
|
const vaultCompare = left.vaultName.localeCompare(right.vaultName);
|
|
5104
3552
|
return vaultCompare !== 0 ? vaultCompare : left.itemName.localeCompare(right.itemName);
|
|
5105
3553
|
});
|
|
5106
3554
|
}
|
|
5107
3555
|
async function renderVaultMetadataRows(globalOpts) {
|
|
5108
|
-
const spinner =
|
|
3556
|
+
const spinner = ora23("Fetching vault item metadata...").start();
|
|
5109
3557
|
const rows = await loadVaultMetadataRows(globalOpts);
|
|
5110
3558
|
spinner.stop();
|
|
5111
3559
|
if (globalOpts.json) {
|
|
@@ -5123,7 +3571,7 @@ async function renderVaultMetadataRows(globalOpts) {
|
|
|
5123
3571
|
row.vaultName,
|
|
5124
3572
|
row.itemName,
|
|
5125
3573
|
row.type || "-",
|
|
5126
|
-
String(row.fieldCount),
|
|
3574
|
+
row.fieldCount === null ? "-" : String(row.fieldCount),
|
|
5127
3575
|
row.groupName || "-",
|
|
5128
3576
|
row.websites.join(", ") || "-"
|
|
5129
3577
|
]),
|
|
@@ -5132,7 +3580,7 @@ async function renderVaultMetadataRows(globalOpts) {
|
|
|
5132
3580
|
console.log();
|
|
5133
3581
|
}
|
|
5134
3582
|
function itemsCommand() {
|
|
5135
|
-
return new
|
|
3583
|
+
return new Command34("items").description("List vault items from the decrypted env map, or use --metadata-only for raw machine metadata").option("--metadata-only", "List item metadata without local decryption").action(
|
|
5136
3584
|
withErrorHandler(async (opts, cmd) => {
|
|
5137
3585
|
const globalOpts = cmd.optsWithGlobals();
|
|
5138
3586
|
if (opts.metadataOnly) {
|
|
@@ -5140,8 +3588,8 @@ function itemsCommand() {
|
|
|
5140
3588
|
return;
|
|
5141
3589
|
}
|
|
5142
3590
|
const config = resolveAuth(globalOpts);
|
|
5143
|
-
const spinner =
|
|
5144
|
-
const r4 = await
|
|
3591
|
+
const spinner = ora23("Fetching vault items...").start();
|
|
3592
|
+
const r4 = await R44.create(config);
|
|
5145
3593
|
spinner.stop();
|
|
5146
3594
|
const env = r4.env;
|
|
5147
3595
|
const items = deriveItems(env);
|
|
@@ -5167,7 +3615,7 @@ function itemsCommand() {
|
|
|
5167
3615
|
|
|
5168
3616
|
// src/commands/vault/list-items.ts
|
|
5169
3617
|
function listItemsCommand() {
|
|
5170
|
-
return new
|
|
3618
|
+
return new Command35("list-items").description("List vault item metadata only, without requiring local decryption").action(
|
|
5171
3619
|
withErrorHandler(async (_opts, cmd) => {
|
|
5172
3620
|
const globalOpts = cmd.optsWithGlobals();
|
|
5173
3621
|
await renderVaultMetadataRows(globalOpts);
|
|
@@ -5176,15 +3624,15 @@ function listItemsCommand() {
|
|
|
5176
3624
|
}
|
|
5177
3625
|
|
|
5178
3626
|
// src/commands/vault/list-vaults.ts
|
|
5179
|
-
import { Command as
|
|
5180
|
-
import
|
|
3627
|
+
import { Command as Command36 } from "commander";
|
|
3628
|
+
import ora24 from "ora";
|
|
5181
3629
|
function listVaultsCommand() {
|
|
5182
|
-
return new
|
|
3630
|
+
return new Command36("list-vaults").description("List visible vaults without requiring local decryption").action(
|
|
5183
3631
|
withErrorHandler(async (_opts, cmd) => {
|
|
5184
3632
|
const globalOpts = cmd.optsWithGlobals();
|
|
5185
3633
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5186
3634
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5187
|
-
const spinner =
|
|
3635
|
+
const spinner = ora24("Fetching visible vaults...").start();
|
|
5188
3636
|
const response = await client.listVaults(connection.projectId);
|
|
5189
3637
|
spinner.stop();
|
|
5190
3638
|
if (globalOpts.json) {
|
|
@@ -5213,17 +3661,17 @@ function listVaultsCommand() {
|
|
|
5213
3661
|
}
|
|
5214
3662
|
|
|
5215
3663
|
// src/commands/vault/get.ts
|
|
5216
|
-
import { Command as
|
|
5217
|
-
import
|
|
5218
|
-
import
|
|
3664
|
+
import { Command as Command37 } from "commander";
|
|
3665
|
+
import ora25 from "ora";
|
|
3666
|
+
import R45 from "@r4-sdk/node";
|
|
5219
3667
|
function getCommand2() {
|
|
5220
|
-
return new
|
|
3668
|
+
return new Command37("get").description("Get a specific locally decrypted environment variable value").argument("<key>", "Environment variable key (SCREAMING_SNAKE_CASE)").action(
|
|
5221
3669
|
withErrorHandler(
|
|
5222
3670
|
async (keyArg, _opts, cmd) => {
|
|
5223
3671
|
const globalOpts = cmd.optsWithGlobals();
|
|
5224
3672
|
const config = resolveAuth(globalOpts);
|
|
5225
|
-
const spinner =
|
|
5226
|
-
const r4 = await
|
|
3673
|
+
const spinner = ora25("Fetching environment variables...").start();
|
|
3674
|
+
const r4 = await R45.create(config);
|
|
5227
3675
|
spinner.stop();
|
|
5228
3676
|
const env = r4.env;
|
|
5229
3677
|
const key = keyArg.toUpperCase();
|
|
@@ -5243,17 +3691,17 @@ function getCommand2() {
|
|
|
5243
3691
|
}
|
|
5244
3692
|
|
|
5245
3693
|
// src/commands/vault/search.ts
|
|
5246
|
-
import { Command as
|
|
5247
|
-
import
|
|
5248
|
-
import
|
|
3694
|
+
import { Command as Command38 } from "commander";
|
|
3695
|
+
import ora26 from "ora";
|
|
3696
|
+
import R46 from "@r4-sdk/node";
|
|
5249
3697
|
function searchCommand() {
|
|
5250
|
-
return new
|
|
3698
|
+
return new Command38("search").description("Search vault items by name").argument("<query>", "Search query (case-insensitive match against key names)").action(
|
|
5251
3699
|
withErrorHandler(
|
|
5252
3700
|
async (query, _opts, cmd) => {
|
|
5253
3701
|
const globalOpts = cmd.optsWithGlobals();
|
|
5254
3702
|
const config = resolveAuth(globalOpts);
|
|
5255
|
-
const spinner =
|
|
5256
|
-
const r4 = await
|
|
3703
|
+
const spinner = ora26("Searching vault items...").start();
|
|
3704
|
+
const r4 = await R46.create(config);
|
|
5257
3705
|
spinner.stop();
|
|
5258
3706
|
const env = r4.env;
|
|
5259
3707
|
const lowerQuery = query.toLowerCase();
|
|
@@ -5283,6 +3731,7 @@ function registerVaultCommands(program2) {
|
|
|
5283
3731
|
const vault = program2.command("vault").description("Manage vault secrets");
|
|
5284
3732
|
vault.addCommand(createCommand3());
|
|
5285
3733
|
vault.addCommand(createItemCommand());
|
|
3734
|
+
vault.addCommand(downloadAssetCommand());
|
|
5286
3735
|
vault.addCommand(listCommand5());
|
|
5287
3736
|
vault.addCommand(listVaultsCommand());
|
|
5288
3737
|
vault.addCommand(listItemsCommand());
|
|
@@ -5292,16 +3741,16 @@ function registerVaultCommands(program2) {
|
|
|
5292
3741
|
}
|
|
5293
3742
|
|
|
5294
3743
|
// src/commands/project/add-vault.ts
|
|
5295
|
-
import { Command as
|
|
5296
|
-
import
|
|
3744
|
+
import { Command as Command39 } from "commander";
|
|
3745
|
+
import ora27 from "ora";
|
|
5297
3746
|
function addVaultCommand() {
|
|
5298
|
-
return new
|
|
3747
|
+
return new Command39("add-vault").description("Associate a vault with a project").argument("<projectId>", "Project ID").argument("<vaultId>", "Vault ID").action(
|
|
5299
3748
|
withErrorHandler(
|
|
5300
3749
|
async (projectId, vaultId, _opts, cmd) => {
|
|
5301
3750
|
const globalOpts = cmd.optsWithGlobals();
|
|
5302
3751
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5303
3752
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5304
|
-
const spinner =
|
|
3753
|
+
const spinner = ora27("Associating vault with project...").start();
|
|
5305
3754
|
await client.associateProjectVault({
|
|
5306
3755
|
projectId,
|
|
5307
3756
|
vaultId
|
|
@@ -5318,15 +3767,15 @@ function addVaultCommand() {
|
|
|
5318
3767
|
}
|
|
5319
3768
|
|
|
5320
3769
|
// src/commands/project/list.ts
|
|
5321
|
-
import { Command as
|
|
5322
|
-
import
|
|
3770
|
+
import { Command as Command40 } from "commander";
|
|
3771
|
+
import ora28 from "ora";
|
|
5323
3772
|
function listCommand6() {
|
|
5324
|
-
return new
|
|
3773
|
+
return new Command40("list").description("List all projects").action(
|
|
5325
3774
|
withErrorHandler(async (_opts, cmd) => {
|
|
5326
3775
|
const globalOpts = cmd.optsWithGlobals();
|
|
5327
3776
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5328
3777
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5329
|
-
const spinner =
|
|
3778
|
+
const spinner = ora28("Fetching projects...").start();
|
|
5330
3779
|
const response = await client.listProjects();
|
|
5331
3780
|
spinner.stop();
|
|
5332
3781
|
const rows = response.projects.map((p) => [
|
|
@@ -5350,16 +3799,16 @@ function listCommand6() {
|
|
|
5350
3799
|
}
|
|
5351
3800
|
|
|
5352
3801
|
// src/commands/project/get.ts
|
|
5353
|
-
import { Command as
|
|
3802
|
+
import { Command as Command41 } from "commander";
|
|
5354
3803
|
import chalk6 from "chalk";
|
|
5355
|
-
import
|
|
3804
|
+
import ora29 from "ora";
|
|
5356
3805
|
function getCommand3() {
|
|
5357
|
-
return new
|
|
3806
|
+
return new Command41("get").description("Get project details").argument("<id>", "Project ID").action(
|
|
5358
3807
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
5359
3808
|
const globalOpts = cmd.optsWithGlobals();
|
|
5360
3809
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5361
3810
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5362
|
-
const spinner =
|
|
3811
|
+
const spinner = ora29("Fetching project...").start();
|
|
5363
3812
|
const project = await client.getProject(id);
|
|
5364
3813
|
spinner.stop();
|
|
5365
3814
|
if (globalOpts.json) {
|
|
@@ -5417,9 +3866,9 @@ function getCommand3() {
|
|
|
5417
3866
|
}
|
|
5418
3867
|
|
|
5419
3868
|
// src/commands/project/create.ts
|
|
5420
|
-
import { Command as
|
|
5421
|
-
import readline2 from "
|
|
5422
|
-
import
|
|
3869
|
+
import { Command as Command42 } from "commander";
|
|
3870
|
+
import readline2 from "readline";
|
|
3871
|
+
import ora30 from "ora";
|
|
5423
3872
|
function prompt(question) {
|
|
5424
3873
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
5425
3874
|
return new Promise((resolve) => {
|
|
@@ -5430,7 +3879,7 @@ function prompt(question) {
|
|
|
5430
3879
|
});
|
|
5431
3880
|
}
|
|
5432
3881
|
function createCommand4() {
|
|
5433
|
-
return new
|
|
3882
|
+
return new Command42("create").description("Create a new project").option("--name <name>", "Project name").option("--description <description>", "Project description").option("--external-id <externalId>", "External identifier").action(
|
|
5434
3883
|
withErrorHandler(async (opts, cmd) => {
|
|
5435
3884
|
const globalOpts = cmd.optsWithGlobals();
|
|
5436
3885
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
@@ -5442,7 +3891,7 @@ function createCommand4() {
|
|
|
5442
3891
|
if (!name) {
|
|
5443
3892
|
throw new Error("Project name is required.");
|
|
5444
3893
|
}
|
|
5445
|
-
const spinner =
|
|
3894
|
+
const spinner = ora30("Creating project...").start();
|
|
5446
3895
|
const response = await client.createProject({
|
|
5447
3896
|
name,
|
|
5448
3897
|
description: opts.description,
|
|
@@ -5459,10 +3908,10 @@ function createCommand4() {
|
|
|
5459
3908
|
}
|
|
5460
3909
|
|
|
5461
3910
|
// src/commands/project/set-agents.ts
|
|
5462
|
-
import { Command as
|
|
5463
|
-
import
|
|
3911
|
+
import { Command as Command43 } from "commander";
|
|
3912
|
+
import ora31 from "ora";
|
|
5464
3913
|
function setAgentsCommand() {
|
|
5465
|
-
return new
|
|
3914
|
+
return new Command43("set-agents").description("Replace the explicit agent membership for a project").argument("<projectId>", "Project ID").option(
|
|
5466
3915
|
"--agent-id <id>",
|
|
5467
3916
|
"Agent ID to keep on the project (repeat or use comma-separated values)",
|
|
5468
3917
|
collectOptionValues,
|
|
@@ -5473,7 +3922,7 @@ function setAgentsCommand() {
|
|
|
5473
3922
|
const globalOpts = cmd.optsWithGlobals();
|
|
5474
3923
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5475
3924
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5476
|
-
const spinner =
|
|
3925
|
+
const spinner = ora31("Updating project agents...").start();
|
|
5477
3926
|
await client.updateProjectAgents(projectId, {
|
|
5478
3927
|
agentIds: options.agentId
|
|
5479
3928
|
});
|
|
@@ -5499,17 +3948,17 @@ function registerProjectCommands(program2) {
|
|
|
5499
3948
|
}
|
|
5500
3949
|
|
|
5501
3950
|
// src/commands/run/index.ts
|
|
5502
|
-
import { spawn } from "
|
|
5503
|
-
import
|
|
5504
|
-
import
|
|
3951
|
+
import { spawn } from "child_process";
|
|
3952
|
+
import ora32 from "ora";
|
|
3953
|
+
import R47 from "@r4-sdk/node";
|
|
5505
3954
|
function registerRunCommand(program2) {
|
|
5506
3955
|
program2.command("run").description("Run a command with vault secrets injected as environment variables").argument("<command...>", "Command and arguments to execute").option("--prefix <prefix>", "Add prefix to all injected env var names").action(
|
|
5507
3956
|
withErrorHandler(
|
|
5508
3957
|
async (commandParts, opts, cmd) => {
|
|
5509
3958
|
const globalOpts = cmd.optsWithGlobals();
|
|
5510
3959
|
const config = resolveAuth(globalOpts);
|
|
5511
|
-
const spinner =
|
|
5512
|
-
const r4 = await
|
|
3960
|
+
const spinner = ora32("Loading vault secrets...").start();
|
|
3961
|
+
const r4 = await R47.create(config);
|
|
5513
3962
|
spinner.stop();
|
|
5514
3963
|
const env = r4.env;
|
|
5515
3964
|
const secretEnv = {};
|
|
@@ -5535,10 +3984,10 @@ function registerRunCommand(program2) {
|
|
|
5535
3984
|
}
|
|
5536
3985
|
|
|
5537
3986
|
// src/commands/security-group/create.ts
|
|
5538
|
-
import { Command as
|
|
5539
|
-
import
|
|
3987
|
+
import { Command as Command44 } from "commander";
|
|
3988
|
+
import ora33 from "ora";
|
|
5540
3989
|
function createCommand5() {
|
|
5541
|
-
return new
|
|
3990
|
+
return new Command44("create").description("Create a tenant security group").requiredOption("--name <name>", "Security group name").requiredOption("--tenant-id <id>", "Tenant ID").option("--parent-id <id>", "Optional parent security group ID").option(
|
|
5542
3991
|
"--role <role>",
|
|
5543
3992
|
"Tenant role to grant through this group (repeat or use comma-separated values)",
|
|
5544
3993
|
collectOptionValues,
|
|
@@ -5549,7 +3998,7 @@ function createCommand5() {
|
|
|
5549
3998
|
const globalOpts = cmd.optsWithGlobals();
|
|
5550
3999
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5551
4000
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5552
|
-
const spinner =
|
|
4001
|
+
const spinner = ora33("Creating security group...").start();
|
|
5553
4002
|
await client.createSecurityGroup({
|
|
5554
4003
|
name: options.name,
|
|
5555
4004
|
parentId: options.parentId ?? null,
|
|
@@ -5585,8 +4034,8 @@ function registerSecurityGroupCommands(program2) {
|
|
|
5585
4034
|
}
|
|
5586
4035
|
|
|
5587
4036
|
// src/index.ts
|
|
5588
|
-
var program = new
|
|
5589
|
-
program.name("r4").description("R4 CLI \u2014 manage vaults, projects, and secrets from the terminal").version("1.0.
|
|
4037
|
+
var program = new Command45();
|
|
4038
|
+
program.name("r4").description("R4 CLI \u2014 manage vaults, projects, and secrets from the terminal").version("1.0.2").option("--api-key <key>", "API key (overrides R4_API_KEY env var and config file)").option("--profile <name>", "CLI profile name (overrides R4_PROFILE and the saved current profile)").option("--project-id <id>", "Optional project ID filter (overrides R4_PROJECT_ID env var and config file)").option("--dev", "Use https://dev.r4.dev unless an explicit base URL override is set").option("--base-url <url>", "API base URL (default: https://r4.dev)").option("--private-key-path <path>", "Path to the agent private key PEM (overrides R4_PRIVATE_KEY_PATH env var and config file)").option("--trust-store-path <path>", "Path to the local signer trust-store JSON (overrides R4_TRUST_STORE_PATH env var and config file)").option("--json", "Output as JSON for scripting and piping", false);
|
|
5590
4039
|
registerAgentCommands(program);
|
|
5591
4040
|
registerAuthCommands(program);
|
|
5592
4041
|
registerBillingCommands(program);
|