@r4-sdk/cli 1.0.1 → 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 +20 -3
- package/lib/index.js +302 -1870
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
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(
|
|
@@ -307,14 +342,14 @@ import fs2 from "fs";
|
|
|
307
342
|
|
|
308
343
|
// src/lib/profile-paths.ts
|
|
309
344
|
import fs from "fs";
|
|
310
|
-
import
|
|
345
|
+
import os2 from "os";
|
|
311
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");
|
|
@@ -890,1792 +925,117 @@ function createCommand() {
|
|
|
890
925
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
891
926
|
const body = buildCreateAgentBody(options);
|
|
892
927
|
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 "crypto";
|
|
1004
|
-
import path3 from "path";
|
|
1005
|
-
import crypto2 from "crypto";
|
|
1006
|
-
import fs5 from "fs";
|
|
1007
|
-
import path4 from "path";
|
|
1008
|
-
import fs22 from "fs";
|
|
1009
|
-
import path22 from "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 nestedError = typeof errorBody.error === "object" && errorBody.error !== null ? errorBody.error : null;
|
|
1041
|
-
const topLevelMessage = Array.isArray(errorBody.message) ? errorBody.message.filter((item) => typeof item === "string").join("; ") : typeof errorBody.message === "string" ? errorBody.message : null;
|
|
1042
|
-
const errorMessage = typeof nestedError?.message === "string" ? nestedError.message : topLevelMessage || (typeof errorBody.error === "string" ? errorBody.error : null) || `HTTP ${response.status}: ${response.statusText}`;
|
|
1043
|
-
const errorCode = typeof nestedError?.code === "string" ? ` [${nestedError.code}]` : "";
|
|
1044
|
-
throw new Error(`R4 API Error${errorCode}: ${errorMessage}`);
|
|
1045
|
-
}
|
|
1046
|
-
if (response.status === 204) {
|
|
1047
|
-
return void 0;
|
|
1048
|
-
}
|
|
1049
|
-
const responseText = await response.text();
|
|
1050
|
-
if (!responseText) {
|
|
1051
|
-
return void 0;
|
|
1052
|
-
}
|
|
1053
|
-
try {
|
|
1054
|
-
return JSON.parse(responseText);
|
|
1055
|
-
} catch {
|
|
1056
|
-
return responseText;
|
|
1057
|
-
}
|
|
1058
|
-
}
|
|
1059
|
-
/**
|
|
1060
|
-
* Calls any existing machine API route through the authenticated client.
|
|
1061
|
-
* Accepts either `/api/v1/machine/...` or the shorter `/...` machine path.
|
|
1062
|
-
*/
|
|
1063
|
-
async requestMachine(params) {
|
|
1064
|
-
return this.request(this.normalizeMachinePath(params.path), {
|
|
1065
|
-
method: params.method,
|
|
1066
|
-
body: params.body === void 0 ? void 0 : JSON.stringify(params.body)
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
/**
|
|
1070
|
-
* Registers or re-confirms the agent runtime's local RSA public key.
|
|
1071
|
-
*/
|
|
1072
|
-
async registerAgentPublicKey(body) {
|
|
1073
|
-
return this.request("/api/v1/machine/vault/public-key", {
|
|
1074
|
-
method: "POST",
|
|
1075
|
-
body: JSON.stringify(body)
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Returns the current machine principal, session context, and resolved policy.
|
|
1080
|
-
*/
|
|
1081
|
-
async getMachineIdentity() {
|
|
1082
|
-
return this.request("/api/v1/machine/me", {
|
|
1083
|
-
method: "GET"
|
|
1084
|
-
});
|
|
1085
|
-
}
|
|
1086
|
-
/**
|
|
1087
|
-
* Lists all accessible non-hidden vaults. When `projectId` is provided, the
|
|
1088
|
-
* backend additionally filters to vaults associated with that project.
|
|
1089
|
-
*/
|
|
1090
|
-
async listVaults(projectId) {
|
|
1091
|
-
const search = projectId ? `?projectId=${encodeURIComponent(projectId)}` : "";
|
|
1092
|
-
return this.request(`/api/v1/machine/vault${search}`, {
|
|
1093
|
-
method: "GET"
|
|
1094
|
-
});
|
|
1095
|
-
}
|
|
1096
|
-
/**
|
|
1097
|
-
* Retrieves the active wrapped DEK for the authenticated agent on a vault.
|
|
1098
|
-
*/
|
|
1099
|
-
async getAgentWrappedKey(vaultId) {
|
|
1100
|
-
return this.request(
|
|
1101
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/wrapped-key`,
|
|
1102
|
-
{ method: "GET" }
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
/**
|
|
1106
|
-
* Retrieves the trusted user-key directory for a vault so the runtime can
|
|
1107
|
-
* verify wrapped-DEK signatures locally.
|
|
1108
|
-
*/
|
|
1109
|
-
async getVaultUserKeyDirectory(vaultId, params) {
|
|
1110
|
-
const searchParams = new URLSearchParams();
|
|
1111
|
-
if (params?.knownTransparencyVersion !== void 0) {
|
|
1112
|
-
searchParams.set("knownTransparencyVersion", String(params.knownTransparencyVersion));
|
|
1113
|
-
}
|
|
1114
|
-
if (params?.knownTransparencyHash) {
|
|
1115
|
-
searchParams.set("knownTransparencyHash", params.knownTransparencyHash);
|
|
1116
|
-
}
|
|
1117
|
-
const search = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
|
|
1118
|
-
return this.request(
|
|
1119
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/public-keys${search}`,
|
|
1120
|
-
{ method: "GET" }
|
|
1121
|
-
);
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Lists all items in a vault with lightweight metadata.
|
|
1125
|
-
*/
|
|
1126
|
-
async listVaultItems(vaultId) {
|
|
1127
|
-
return this.request(
|
|
1128
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items`,
|
|
1129
|
-
{ method: "GET" }
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
/**
|
|
1133
|
-
* Retrieves the full field payloads for a vault item.
|
|
1134
|
-
*/
|
|
1135
|
-
async getVaultItemDetail(vaultId, itemId) {
|
|
1136
|
-
return this.request(
|
|
1137
|
-
`/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items/${encodeURIComponent(itemId)}`,
|
|
1138
|
-
{ method: "GET" }
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1141
|
-
/**
|
|
1142
|
-
* Retrieves the provider-scoped runtime bundle needed for local decryption.
|
|
1143
|
-
*/
|
|
1144
|
-
async getTokenProviderRuntime(tokenProviderId, params) {
|
|
1145
|
-
const searchParams = new URLSearchParams();
|
|
1146
|
-
if (params?.knownTransparencyVersion !== void 0) {
|
|
1147
|
-
searchParams.set("knownTransparencyVersion", String(params.knownTransparencyVersion));
|
|
1148
|
-
}
|
|
1149
|
-
if (params?.knownTransparencyHash) {
|
|
1150
|
-
searchParams.set("knownTransparencyHash", params.knownTransparencyHash);
|
|
1151
|
-
}
|
|
1152
|
-
const search = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
|
|
1153
|
-
return this.request(
|
|
1154
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/runtime${search}`,
|
|
1155
|
-
{ method: "GET" }
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
/**
|
|
1159
|
-
* Runs the managed budget gate before a token-provider call is sent to the vendor.
|
|
1160
|
-
*/
|
|
1161
|
-
async authorizeTokenProviderUsage(tokenProviderId, body) {
|
|
1162
|
-
return this.request(
|
|
1163
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/authorize-usage`,
|
|
1164
|
-
{
|
|
1165
|
-
method: "POST",
|
|
1166
|
-
body: JSON.stringify(body)
|
|
1167
|
-
}
|
|
1168
|
-
);
|
|
1169
|
-
}
|
|
1170
|
-
/**
|
|
1171
|
-
* Publishes finalized token usage for a completed managed provider call.
|
|
1172
|
-
*/
|
|
1173
|
-
async reportTokenProviderUsage(tokenProviderId, body) {
|
|
1174
|
-
return this.request(
|
|
1175
|
-
`/api/v1/machine/intelligence-provider/${encodeURIComponent(tokenProviderId)}/report-usage`,
|
|
1176
|
-
{
|
|
1177
|
-
method: "POST",
|
|
1178
|
-
body: JSON.stringify(body)
|
|
1179
|
-
}
|
|
1180
|
-
);
|
|
1181
|
-
}
|
|
1182
|
-
};
|
|
1183
|
-
var TRANSPARENCY_WITNESS_PAYLOAD_PREFIX = "r4-transparency-witness-v1";
|
|
1184
|
-
var DEFAULT_TRANSPARENCY_WITNESS_URL = "https://transparency-prod.r4.dev";
|
|
1185
|
-
var DEV_TRANSPARENCY_WITNESS_URL = "https://transparency-dev.r4.dev";
|
|
1186
|
-
var TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1187
|
-
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JhILFiS/BOWR9laubW
|
|
1188
|
-
g2vepQy26BXAlnrscZZVQUzBBaCM4hWobpt3Nh77vxP0gqVAJXP1hVhPPwxGQnOF
|
|
1189
|
-
4Qg/RK4iEETjMdmh3KMqFX9MeE9tP4cTOGtsgWsedNpu6TvMT+2vu+0ltmr7p4Xv
|
|
1190
|
-
H0ID48Q8JLeNksc/RekrsfzQ9DVtXFS7z1FF2VQgzamdJsW9hGMiM7Q+0iXei7PW
|
|
1191
|
-
3PsLd1aNtqJ3lIj3t12qFiJiYyKF0hEq0//Abgb9SgDv/WOlRG1Ianf1/fnP2jer
|
|
1192
|
-
ZYiZSylXqQdun0Db2d0+FDm/znV2AGAmBEXm6qnCogEHu77LoLyCyJOlB9WNtRwh
|
|
1193
|
-
KnbzTmE2Mw/43jxvCcR7pE5kik/tdeMvqGFZfg3ozUG9eM0q0TURH6g9b9J4sBnR
|
|
1194
|
-
dxz2PbF4cl/AeL4ANPmLz3kUQaDA6wR0veVk5jV+Uqr55TYz/zEbY1rtJbmnc53Q
|
|
1195
|
-
ihPS6xtSiexrqnOgqm/AVbiRhxjPqfg3/VJM3zR5Blnu02AqVR9kCT0WkyEWRz5X
|
|
1196
|
-
6HU8DEocJIPz8UwBMKQ7rnjMPv/Fjpuav/EIad5vOdfxCZkjyTYoQg8vLUyfXvgD
|
|
1197
|
-
mBWFgKIN8GTRyM+LjZIgznjN58dZ8ZvsGd14oKnH7WgAh9FVh8ri7gNmsdJeRTn/
|
|
1198
|
-
2zDkTlx+FQxAxqFaYV7qCvcCAwEAAQ==
|
|
1199
|
-
-----END PUBLIC KEY-----`;
|
|
1200
|
-
var buildOrgUserKeyDirectoryWitnessPayload = (orgId, head) => [
|
|
1201
|
-
TRANSPARENCY_WITNESS_PAYLOAD_PREFIX,
|
|
1202
|
-
"org-user-key-directory",
|
|
1203
|
-
orgId,
|
|
1204
|
-
String(head.version),
|
|
1205
|
-
head.hash
|
|
1206
|
-
].join(":");
|
|
1207
|
-
var buildAgentPublicKeyWitnessPayload = (agentId, head) => [
|
|
1208
|
-
TRANSPARENCY_WITNESS_PAYLOAD_PREFIX,
|
|
1209
|
-
"agent-public-key",
|
|
1210
|
-
agentId,
|
|
1211
|
-
String(head.version),
|
|
1212
|
-
head.hash
|
|
1213
|
-
].join(":");
|
|
1214
|
-
var buildOrgUserKeyDirectoryWitnessPath = (orgId) => `v1/orgs/${orgId}/user-key-directory-head.json`;
|
|
1215
|
-
var buildAgentPublicKeyWitnessPath = (agentId) => `v1/agents/${agentId}/public-key-head.json`;
|
|
1216
|
-
var buildTransparencyWitnessUrl = (baseUrl, path42) => `${baseUrl.replace(/\/+$/, "")}/${path42.replace(/^\/+/, "")}`;
|
|
1217
|
-
function parseHostname(apiBaseUrl) {
|
|
1218
|
-
const trimmed = apiBaseUrl.trim();
|
|
1219
|
-
if (!trimmed) {
|
|
1220
|
-
return null;
|
|
1221
|
-
}
|
|
1222
|
-
try {
|
|
1223
|
-
const normalized = trimmed.includes("://") ? trimmed : `https://${trimmed}`;
|
|
1224
|
-
return new URL(normalized).hostname.toLowerCase();
|
|
1225
|
-
} catch {
|
|
1226
|
-
return null;
|
|
1227
|
-
}
|
|
1228
|
-
}
|
|
1229
|
-
var resolveDefaultTransparencyWitnessBaseUrl = (apiBaseUrl) => {
|
|
1230
|
-
const hostname = parseHostname(apiBaseUrl);
|
|
1231
|
-
if (hostname === "r4.dev" || hostname === "api.r4.dev") {
|
|
1232
|
-
return DEFAULT_TRANSPARENCY_WITNESS_URL;
|
|
1233
|
-
}
|
|
1234
|
-
if (hostname === "dev.r4.dev") {
|
|
1235
|
-
return DEV_TRANSPARENCY_WITNESS_URL;
|
|
1236
|
-
}
|
|
1237
|
-
return null;
|
|
1238
|
-
};
|
|
1239
|
-
var resolveTransparencyWitnessBaseUrl = (params) => {
|
|
1240
|
-
const configuredBaseUrl = params.configuredBaseUrl?.trim();
|
|
1241
|
-
if (configuredBaseUrl) {
|
|
1242
|
-
return configuredBaseUrl;
|
|
1243
|
-
}
|
|
1244
|
-
return resolveDefaultTransparencyWitnessBaseUrl(params.apiBaseUrl);
|
|
1245
|
-
};
|
|
1246
|
-
var RSA_OAEP_CONFIG = {
|
|
1247
|
-
padding: crypto2.constants.RSA_PKCS1_OAEP_PADDING,
|
|
1248
|
-
oaepHash: "sha256"
|
|
1249
|
-
};
|
|
1250
|
-
var RSA_PSS_SIGN_CONFIG = {
|
|
1251
|
-
padding: crypto2.constants.RSA_PKCS1_PSS_PADDING,
|
|
1252
|
-
saltLength: 32
|
|
1253
|
-
};
|
|
1254
|
-
var USER_KEY_ROTATION_PREFIX = "r4-user-key-rotation-v1";
|
|
1255
|
-
var USER_KEY_DIRECTORY_CHECKPOINT_PREFIX = "r4-user-key-directory-checkpoint-v1";
|
|
1256
|
-
var USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX = "r4-user-key-directory-transparency-entry-v1";
|
|
1257
|
-
var AGENT_PUBLIC_KEY_TRANSPARENCY_ENTRY_PREFIX = "r4-agent-public-key-transparency-entry-v1";
|
|
1258
|
-
var WRAPPED_DEK_SIGNATURE_PREFIX = "r4-wrapped-dek-signature-v1";
|
|
1259
|
-
var VAULT_SUMMARY_CHECKPOINT_PREFIX = "r4-vault-summary-checkpoint-v1";
|
|
1260
|
-
var VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX = "r4-vault-item-detail-checkpoint-v1";
|
|
1261
|
-
function pemToDer(pem, beginLabel, endLabel) {
|
|
1262
|
-
const derBase64 = pem.replace(beginLabel, "").replace(endLabel, "").replace(/\s/g, "");
|
|
1263
|
-
return Buffer.from(derBase64, "base64");
|
|
1264
|
-
}
|
|
1265
|
-
function getWrappedDekFingerprint(wrappedDek) {
|
|
1266
|
-
return crypto2.createHash("sha256").update(Buffer.from(wrappedDek, "base64")).digest("hex");
|
|
1267
|
-
}
|
|
1268
|
-
function getCheckpointFingerprint(prefix, canonicalJson) {
|
|
1269
|
-
return `${prefix}:${crypto2.createHash("sha256").update(canonicalJson, "utf8").digest("hex")}`;
|
|
1270
|
-
}
|
|
1271
|
-
function loadPrivateKey2(privateKeyPath) {
|
|
1272
|
-
return fs5.readFileSync(path4.resolve(privateKeyPath), "utf8").trim();
|
|
1273
|
-
}
|
|
1274
|
-
function derivePublicKey2(privateKeyPem) {
|
|
1275
|
-
return crypto2.createPublicKey(privateKeyPem).export({
|
|
1276
|
-
type: "spki",
|
|
1277
|
-
format: "pem"
|
|
1278
|
-
}).toString();
|
|
1279
|
-
}
|
|
1280
|
-
function getPublicKeyFingerprint(publicKeyPem) {
|
|
1281
|
-
const derBytes = pemToDer(publicKeyPem, "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----");
|
|
1282
|
-
return crypto2.createHash("sha256").update(derBytes).digest("hex");
|
|
1283
|
-
}
|
|
1284
|
-
function buildUserKeyRotationPayload(previousUserKeyPairId, newPublicKeyFingerprint) {
|
|
1285
|
-
return `${USER_KEY_ROTATION_PREFIX}:${previousUserKeyPairId}:${newPublicKeyFingerprint}`;
|
|
1286
|
-
}
|
|
1287
|
-
function verifyUserKeyRotation(previousUserKeyPairId, newPublicKeyPem, rotationSignature, previousPublicKeyPem) {
|
|
1288
|
-
const payload = buildUserKeyRotationPayload(
|
|
1289
|
-
previousUserKeyPairId,
|
|
1290
|
-
getPublicKeyFingerprint(newPublicKeyPem)
|
|
1291
|
-
);
|
|
1292
|
-
try {
|
|
1293
|
-
return crypto2.verify(
|
|
1294
|
-
"sha256",
|
|
1295
|
-
Buffer.from(payload, "utf8"),
|
|
1296
|
-
{
|
|
1297
|
-
key: previousPublicKeyPem,
|
|
1298
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1299
|
-
},
|
|
1300
|
-
Buffer.from(rotationSignature, "base64")
|
|
1301
|
-
);
|
|
1302
|
-
} catch {
|
|
1303
|
-
return false;
|
|
1304
|
-
}
|
|
1305
|
-
}
|
|
1306
|
-
function verifyTransparencyWitnessPayload(payload, signature, publicKeyPem) {
|
|
1307
|
-
try {
|
|
1308
|
-
return crypto2.verify(
|
|
1309
|
-
"sha256",
|
|
1310
|
-
Buffer.from(payload, "utf8"),
|
|
1311
|
-
{
|
|
1312
|
-
key: publicKeyPem,
|
|
1313
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1314
|
-
},
|
|
1315
|
-
Buffer.from(signature, "base64")
|
|
1316
|
-
);
|
|
1317
|
-
} catch {
|
|
1318
|
-
return false;
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
function verifyOrgUserKeyDirectoryWitnessArtifact(artifact, publicKeyPem) {
|
|
1322
|
-
return verifyTransparencyWitnessPayload(
|
|
1323
|
-
buildOrgUserKeyDirectoryWitnessPayload(artifact.orgId, artifact.head),
|
|
1324
|
-
artifact.signature,
|
|
1325
|
-
publicKeyPem
|
|
1326
|
-
);
|
|
1327
|
-
}
|
|
1328
|
-
function verifyAgentPublicKeyWitnessArtifact(artifact, publicKeyPem) {
|
|
1329
|
-
return verifyTransparencyWitnessPayload(
|
|
1330
|
-
buildAgentPublicKeyWitnessPayload(artifact.agentId, artifact.head),
|
|
1331
|
-
artifact.signature,
|
|
1332
|
-
publicKeyPem
|
|
1333
|
-
);
|
|
1334
|
-
}
|
|
1335
|
-
function buildAgentPublicKeyTransparencyEntryHash(entry) {
|
|
1336
|
-
return getCheckpointFingerprint(
|
|
1337
|
-
AGENT_PUBLIC_KEY_TRANSPARENCY_ENTRY_PREFIX,
|
|
1338
|
-
JSON.stringify({
|
|
1339
|
-
agentId: entry.agentId,
|
|
1340
|
-
version: entry.version,
|
|
1341
|
-
encryptionKeyId: entry.encryptionKeyId,
|
|
1342
|
-
publicKey: entry.publicKey,
|
|
1343
|
-
fingerprint: entry.fingerprint,
|
|
1344
|
-
previousEncryptionKeyId: entry.previousEncryptionKeyId ?? null,
|
|
1345
|
-
rotationSignature: entry.rotationSignature ?? null,
|
|
1346
|
-
previousEntryHash: entry.previousEntryHash ?? null
|
|
1347
|
-
})
|
|
1348
|
-
);
|
|
1349
|
-
}
|
|
1350
|
-
function buildAgentPublicKeyTransparencyEntry(params) {
|
|
1351
|
-
const normalized = {
|
|
1352
|
-
agentId: params.agentId,
|
|
1353
|
-
version: params.version,
|
|
1354
|
-
encryptionKeyId: params.encryptionKeyId,
|
|
1355
|
-
publicKey: params.publicKey,
|
|
1356
|
-
fingerprint: params.fingerprint ?? getPublicKeyFingerprint(params.publicKey),
|
|
1357
|
-
previousEncryptionKeyId: params.previousEncryptionKeyId ?? null,
|
|
1358
|
-
rotationSignature: params.rotationSignature ?? null,
|
|
1359
|
-
previousEntryHash: params.previousEntryHash ?? null
|
|
1360
|
-
};
|
|
1361
|
-
return {
|
|
1362
|
-
...normalized,
|
|
1363
|
-
entryHash: buildAgentPublicKeyTransparencyEntryHash(normalized)
|
|
1364
|
-
};
|
|
1365
|
-
}
|
|
1366
|
-
function verifyAgentPublicKeyTransparencyChain(entries) {
|
|
1367
|
-
let previousVersion = null;
|
|
1368
|
-
let previousHash = null;
|
|
1369
|
-
for (const entry of entries) {
|
|
1370
|
-
if (buildAgentPublicKeyTransparencyEntryHash(entry) !== entry.entryHash) {
|
|
1371
|
-
return false;
|
|
1372
|
-
}
|
|
1373
|
-
if (entry.previousEntryHash !== previousHash) {
|
|
1374
|
-
return false;
|
|
1375
|
-
}
|
|
1376
|
-
if (previousVersion !== null && entry.version !== previousVersion + 1) {
|
|
1377
|
-
return false;
|
|
1378
|
-
}
|
|
1379
|
-
previousVersion = entry.version;
|
|
1380
|
-
previousHash = entry.entryHash;
|
|
1381
|
-
}
|
|
1382
|
-
return true;
|
|
1383
|
-
}
|
|
1384
|
-
function buildAgentPublicKeyTransparencyHead(entries) {
|
|
1385
|
-
const lastEntry = entries[entries.length - 1];
|
|
1386
|
-
if (!lastEntry) {
|
|
1387
|
-
return null;
|
|
1388
|
-
}
|
|
1389
|
-
return {
|
|
1390
|
-
version: lastEntry.version,
|
|
1391
|
-
hash: lastEntry.entryHash
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
function verifyAgentPublicKeyTransparencyProof(params) {
|
|
1395
|
-
const currentEntryHead = {
|
|
1396
|
-
version: params.currentEntry.version,
|
|
1397
|
-
hash: params.currentEntry.entryHash
|
|
1398
|
-
};
|
|
1399
|
-
if (params.proof.head.version !== currentEntryHead.version || params.proof.head.hash !== currentEntryHead.hash) {
|
|
1400
|
-
return false;
|
|
1401
|
-
}
|
|
1402
|
-
if (!verifyAgentPublicKeyTransparencyChain(params.proof.entries)) {
|
|
1403
|
-
return false;
|
|
1404
|
-
}
|
|
1405
|
-
const proofHead = buildAgentPublicKeyTransparencyHead(params.proof.entries);
|
|
1406
|
-
if (!proofHead || proofHead.version !== currentEntryHead.version || proofHead.hash !== currentEntryHead.hash) {
|
|
1407
|
-
return false;
|
|
1408
|
-
}
|
|
1409
|
-
if (!params.previousHead) {
|
|
1410
|
-
return params.proof.entries.length > 0;
|
|
1411
|
-
}
|
|
1412
|
-
if (params.currentEntry.version < params.previousHead.version) {
|
|
1413
|
-
return false;
|
|
1414
|
-
}
|
|
1415
|
-
const previousEntry = params.proof.entries.find((entry) => entry.version === params.previousHead.version);
|
|
1416
|
-
if (params.currentEntry.version === params.previousHead.version) {
|
|
1417
|
-
return previousEntry?.entryHash === params.previousHead.hash;
|
|
1418
|
-
}
|
|
1419
|
-
return previousEntry?.entryHash === params.previousHead.hash;
|
|
1420
|
-
}
|
|
1421
|
-
function normalizeUserKeyDirectoryCheckpoint(checkpoint) {
|
|
1422
|
-
return {
|
|
1423
|
-
orgId: checkpoint.orgId,
|
|
1424
|
-
version: checkpoint.version,
|
|
1425
|
-
entries: [...checkpoint.entries].map((entry) => ({
|
|
1426
|
-
userKeyPairId: entry.userKeyPairId,
|
|
1427
|
-
orgUserId: entry.orgUserId,
|
|
1428
|
-
fingerprint: entry.fingerprint,
|
|
1429
|
-
previousUserKeyPairId: entry.previousUserKeyPairId ?? null,
|
|
1430
|
-
rotationSignature: entry.rotationSignature ?? null
|
|
1431
|
-
})).sort((left, right) => left.userKeyPairId.localeCompare(right.userKeyPairId))
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
function canonicalizeUserKeyDirectoryCheckpoint(checkpoint) {
|
|
1435
|
-
return JSON.stringify(normalizeUserKeyDirectoryCheckpoint(checkpoint));
|
|
1436
|
-
}
|
|
1437
|
-
function buildUserKeyDirectoryCheckpointPayload(checkpoint) {
|
|
1438
|
-
return getCheckpointFingerprint(
|
|
1439
|
-
USER_KEY_DIRECTORY_CHECKPOINT_PREFIX,
|
|
1440
|
-
canonicalizeUserKeyDirectoryCheckpoint(checkpoint)
|
|
1441
|
-
);
|
|
1442
|
-
}
|
|
1443
|
-
function verifyUserKeyDirectoryCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1444
|
-
try {
|
|
1445
|
-
return crypto2.verify(
|
|
1446
|
-
"sha256",
|
|
1447
|
-
Buffer.from(buildUserKeyDirectoryCheckpointPayload(checkpoint), "utf8"),
|
|
1448
|
-
{
|
|
1449
|
-
key: publicKeyPem,
|
|
1450
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1451
|
-
},
|
|
1452
|
-
Buffer.from(signature, "base64")
|
|
1453
|
-
);
|
|
1454
|
-
} catch {
|
|
1455
|
-
return false;
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
function buildUserKeyDirectoryTransparencyEntryHash(entry) {
|
|
1459
|
-
return getCheckpointFingerprint(
|
|
1460
|
-
USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX,
|
|
1461
|
-
JSON.stringify({
|
|
1462
|
-
orgId: entry.orgId,
|
|
1463
|
-
version: entry.version,
|
|
1464
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1465
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1466
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1467
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1468
|
-
signature: entry.signature,
|
|
1469
|
-
previousEntryHash: entry.previousEntryHash ?? null
|
|
1470
|
-
})
|
|
1471
|
-
);
|
|
1472
|
-
}
|
|
1473
|
-
function buildUserKeyDirectoryTransparencyEntry(params) {
|
|
1474
|
-
const entryWithoutHash = {
|
|
1475
|
-
orgId: params.checkpoint.orgId,
|
|
1476
|
-
version: params.checkpoint.version,
|
|
1477
|
-
directoryCheckpointPayload: buildUserKeyDirectoryCheckpointPayload(params.checkpoint),
|
|
1478
|
-
signerUserKeyPairId: params.signerUserKeyPairId,
|
|
1479
|
-
signerOrgUserId: params.signerOrgUserId,
|
|
1480
|
-
signerFingerprint: getPublicKeyFingerprint(params.signerPublicKey),
|
|
1481
|
-
signature: params.signature,
|
|
1482
|
-
previousEntryHash: params.previousEntryHash ?? null
|
|
1483
|
-
};
|
|
1484
|
-
return {
|
|
1485
|
-
...entryWithoutHash,
|
|
1486
|
-
entryHash: buildUserKeyDirectoryTransparencyEntryHash(entryWithoutHash)
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
function verifyUserKeyDirectoryTransparencyProof(params) {
|
|
1490
|
-
if (params.proof.head.version !== params.currentEntry.version || params.proof.head.hash !== params.currentEntry.entryHash) {
|
|
1491
|
-
return false;
|
|
1492
|
-
}
|
|
1493
|
-
if (!params.previousHead) {
|
|
1494
|
-
if (params.proof.entries.length === 0) {
|
|
1495
|
-
return false;
|
|
1496
|
-
}
|
|
1497
|
-
for (let index = 0; index < params.proof.entries.length; index++) {
|
|
1498
|
-
const entry = params.proof.entries[index];
|
|
1499
|
-
if (!entry) {
|
|
1500
|
-
return false;
|
|
1501
|
-
}
|
|
1502
|
-
const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
|
|
1503
|
-
orgId: entry.orgId,
|
|
1504
|
-
version: entry.version,
|
|
1505
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1506
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1507
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1508
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1509
|
-
signature: entry.signature,
|
|
1510
|
-
previousEntryHash: entry.previousEntryHash
|
|
1511
|
-
});
|
|
1512
|
-
if (expectedHash !== entry.entryHash) {
|
|
1513
|
-
return false;
|
|
1514
|
-
}
|
|
1515
|
-
if (index === 0) {
|
|
1516
|
-
continue;
|
|
1517
|
-
}
|
|
1518
|
-
const previousEntry = params.proof.entries[index - 1];
|
|
1519
|
-
if (!previousEntry) {
|
|
1520
|
-
return false;
|
|
1521
|
-
}
|
|
1522
|
-
if (entry.previousEntryHash !== previousEntry.entryHash || entry.version !== previousEntry.version + 1) {
|
|
1523
|
-
return false;
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
const lastEntry = params.proof.entries[params.proof.entries.length - 1];
|
|
1527
|
-
return lastEntry?.entryHash === params.currentEntry.entryHash && lastEntry.version === params.currentEntry.version;
|
|
1528
|
-
}
|
|
1529
|
-
if (params.currentEntry.version < params.previousHead.version) {
|
|
1530
|
-
return false;
|
|
1531
|
-
}
|
|
1532
|
-
if (params.currentEntry.version === params.previousHead.version) {
|
|
1533
|
-
if (params.currentEntry.entryHash !== params.previousHead.hash) {
|
|
1534
|
-
return false;
|
|
1535
|
-
}
|
|
1536
|
-
if (params.proof.entries.length === 0) {
|
|
1537
|
-
return true;
|
|
1538
|
-
}
|
|
1539
|
-
return params.proof.entries.length === 1 && params.proof.entries[0]?.entryHash === params.currentEntry.entryHash && params.proof.entries[0]?.version === params.currentEntry.version;
|
|
1540
|
-
}
|
|
1541
|
-
if (params.proof.entries.length === 0) {
|
|
1542
|
-
return false;
|
|
1543
|
-
}
|
|
1544
|
-
let previousVersion = params.previousHead.version;
|
|
1545
|
-
let previousHash = params.previousHead.hash;
|
|
1546
|
-
for (const entry of params.proof.entries) {
|
|
1547
|
-
const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
|
|
1548
|
-
orgId: entry.orgId,
|
|
1549
|
-
version: entry.version,
|
|
1550
|
-
directoryCheckpointPayload: entry.directoryCheckpointPayload,
|
|
1551
|
-
signerUserKeyPairId: entry.signerUserKeyPairId,
|
|
1552
|
-
signerOrgUserId: entry.signerOrgUserId,
|
|
1553
|
-
signerFingerprint: entry.signerFingerprint,
|
|
1554
|
-
signature: entry.signature,
|
|
1555
|
-
previousEntryHash: entry.previousEntryHash
|
|
1556
|
-
});
|
|
1557
|
-
if (expectedHash !== entry.entryHash || entry.previousEntryHash !== previousHash || entry.version !== previousVersion + 1) {
|
|
1558
|
-
return false;
|
|
1559
|
-
}
|
|
1560
|
-
previousVersion = entry.version;
|
|
1561
|
-
previousHash = entry.entryHash;
|
|
1562
|
-
}
|
|
1563
|
-
return previousHash === params.currentEntry.entryHash && previousVersion === params.currentEntry.version;
|
|
1564
|
-
}
|
|
1565
|
-
function buildWrappedDekSignaturePayload(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek) {
|
|
1566
|
-
return [
|
|
1567
|
-
WRAPPED_DEK_SIGNATURE_PREFIX,
|
|
1568
|
-
vaultId,
|
|
1569
|
-
recipientKeyId,
|
|
1570
|
-
signerUserKeyPairId,
|
|
1571
|
-
String(dekVersion),
|
|
1572
|
-
getWrappedDekFingerprint(wrappedDek)
|
|
1573
|
-
].join(":");
|
|
1574
|
-
}
|
|
1575
|
-
function verifyWrappedDekSignature(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek, wrappedDekSignature, signerPublicKeyPem) {
|
|
1576
|
-
const payload = buildWrappedDekSignaturePayload(
|
|
1577
|
-
vaultId,
|
|
1578
|
-
recipientKeyId,
|
|
1579
|
-
signerUserKeyPairId,
|
|
1580
|
-
dekVersion,
|
|
1581
|
-
wrappedDek
|
|
1582
|
-
);
|
|
1583
|
-
try {
|
|
1584
|
-
return crypto2.verify(
|
|
1585
|
-
"sha256",
|
|
1586
|
-
Buffer.from(payload, "utf8"),
|
|
1587
|
-
{
|
|
1588
|
-
key: signerPublicKeyPem,
|
|
1589
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1590
|
-
},
|
|
1591
|
-
Buffer.from(wrappedDekSignature, "base64")
|
|
1592
|
-
);
|
|
1593
|
-
} catch {
|
|
1594
|
-
return false;
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
function normalizeVaultSummaryCheckpoint(checkpoint) {
|
|
1598
|
-
return {
|
|
1599
|
-
vaultId: checkpoint.vaultId,
|
|
1600
|
-
version: checkpoint.version,
|
|
1601
|
-
name: checkpoint.name,
|
|
1602
|
-
dataClassification: checkpoint.dataClassification ?? null,
|
|
1603
|
-
currentDekVersion: checkpoint.currentDekVersion ?? null,
|
|
1604
|
-
items: [...checkpoint.items].map((item) => ({
|
|
1605
|
-
id: item.id,
|
|
1606
|
-
name: item.name,
|
|
1607
|
-
type: item.type ?? null,
|
|
1608
|
-
websites: [...item.websites],
|
|
1609
|
-
groupId: item.groupId ?? null
|
|
1610
|
-
})).sort((left, right) => left.id.localeCompare(right.id)),
|
|
1611
|
-
groups: [...checkpoint.groups].map((group) => ({
|
|
1612
|
-
id: group.id,
|
|
1613
|
-
name: group.name,
|
|
1614
|
-
parentId: group.parentId ?? null
|
|
1615
|
-
})).sort((left, right) => left.id.localeCompare(right.id))
|
|
1616
|
-
};
|
|
1617
|
-
}
|
|
1618
|
-
function canonicalizeVaultSummaryCheckpoint(checkpoint) {
|
|
1619
|
-
return JSON.stringify(normalizeVaultSummaryCheckpoint(checkpoint));
|
|
1620
|
-
}
|
|
1621
|
-
function buildVaultSummaryCheckpointPayload(checkpoint) {
|
|
1622
|
-
return getCheckpointFingerprint(
|
|
1623
|
-
VAULT_SUMMARY_CHECKPOINT_PREFIX,
|
|
1624
|
-
canonicalizeVaultSummaryCheckpoint(checkpoint)
|
|
1625
|
-
);
|
|
1626
|
-
}
|
|
1627
|
-
function verifyVaultSummaryCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1628
|
-
try {
|
|
1629
|
-
return crypto2.verify(
|
|
1630
|
-
"sha256",
|
|
1631
|
-
Buffer.from(buildVaultSummaryCheckpointPayload(checkpoint), "utf8"),
|
|
1632
|
-
{
|
|
1633
|
-
key: publicKeyPem,
|
|
1634
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1635
|
-
},
|
|
1636
|
-
Buffer.from(signature, "base64")
|
|
1637
|
-
);
|
|
1638
|
-
} catch {
|
|
1639
|
-
return false;
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
function normalizeVaultItemDetailCheckpoint(checkpoint) {
|
|
1643
|
-
return {
|
|
1644
|
-
vaultItemId: checkpoint.vaultItemId,
|
|
1645
|
-
vaultId: checkpoint.vaultId,
|
|
1646
|
-
version: checkpoint.version,
|
|
1647
|
-
name: checkpoint.name,
|
|
1648
|
-
type: checkpoint.type ?? null,
|
|
1649
|
-
websites: [...checkpoint.websites],
|
|
1650
|
-
groupId: checkpoint.groupId ?? null,
|
|
1651
|
-
fields: [...checkpoint.fields].map((field) => ({
|
|
1652
|
-
id: field.id,
|
|
1653
|
-
name: field.name,
|
|
1654
|
-
type: field.type,
|
|
1655
|
-
order: field.order,
|
|
1656
|
-
fieldInstanceIds: [...field.fieldInstanceIds].sort(),
|
|
1657
|
-
assetIds: [...field.assetIds].sort()
|
|
1658
|
-
})).sort((left, right) => left.order - right.order || left.id.localeCompare(right.id))
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
|
-
function canonicalizeVaultItemDetailCheckpoint(checkpoint) {
|
|
1662
|
-
return JSON.stringify(normalizeVaultItemDetailCheckpoint(checkpoint));
|
|
1663
|
-
}
|
|
1664
|
-
function buildVaultItemDetailCheckpointPayload(checkpoint) {
|
|
1665
|
-
return getCheckpointFingerprint(
|
|
1666
|
-
VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX,
|
|
1667
|
-
canonicalizeVaultItemDetailCheckpoint(checkpoint)
|
|
1668
|
-
);
|
|
1669
|
-
}
|
|
1670
|
-
function verifyVaultItemDetailCheckpoint(checkpoint, signature, publicKeyPem) {
|
|
1671
|
-
try {
|
|
1672
|
-
return crypto2.verify(
|
|
1673
|
-
"sha256",
|
|
1674
|
-
Buffer.from(buildVaultItemDetailCheckpointPayload(checkpoint), "utf8"),
|
|
1675
|
-
{
|
|
1676
|
-
key: publicKeyPem,
|
|
1677
|
-
...RSA_PSS_SIGN_CONFIG
|
|
1678
|
-
},
|
|
1679
|
-
Buffer.from(signature, "base64")
|
|
1680
|
-
);
|
|
1681
|
-
} catch {
|
|
1682
|
-
return false;
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
function isVaultEnvelope(value) {
|
|
1686
|
-
if (!value.startsWith("{")) {
|
|
1687
|
-
return false;
|
|
1688
|
-
}
|
|
1689
|
-
try {
|
|
1690
|
-
const parsed = JSON.parse(value);
|
|
1691
|
-
return parsed.v === 3;
|
|
1692
|
-
} catch {
|
|
1693
|
-
return false;
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
function decryptWithVaultDEK(encryptedValue, dek) {
|
|
1697
|
-
if (!isVaultEnvelope(encryptedValue)) {
|
|
1698
|
-
throw new Error("Invalid encrypted value: expected v3 vault envelope format");
|
|
1699
|
-
}
|
|
1700
|
-
const envelope = JSON.parse(encryptedValue);
|
|
1701
|
-
const decipher = crypto2.createDecipheriv("aes-256-gcm", dek, Buffer.from(envelope.iv, "base64"));
|
|
1702
|
-
decipher.setAuthTag(Buffer.from(envelope.t, "base64"));
|
|
1703
|
-
const decrypted = Buffer.concat([
|
|
1704
|
-
decipher.update(Buffer.from(envelope.d, "base64")),
|
|
1705
|
-
decipher.final()
|
|
1706
|
-
]);
|
|
1707
|
-
return decrypted.toString("utf8");
|
|
1708
|
-
}
|
|
1709
|
-
function decryptStoredFieldValue(value, dek) {
|
|
1710
|
-
return isVaultEnvelope(value) ? decryptWithVaultDEK(value, dek) : value;
|
|
1711
|
-
}
|
|
1712
|
-
function unwrapDEKWithPrivateKey(wrappedDek, privateKeyPem) {
|
|
1713
|
-
return crypto2.privateDecrypt(
|
|
1714
|
-
{ key: privateKeyPem, ...RSA_OAEP_CONFIG },
|
|
1715
|
-
Buffer.from(wrappedDek, "base64")
|
|
1716
|
-
);
|
|
1717
|
-
}
|
|
1718
|
-
function loadTrustStore(trustStorePath) {
|
|
1719
|
-
try {
|
|
1720
|
-
const raw = fs22.readFileSync(trustStorePath, "utf8");
|
|
1721
|
-
const parsed = JSON.parse(raw);
|
|
1722
|
-
return {
|
|
1723
|
-
version: 1,
|
|
1724
|
-
userKeyPins: parsed.userKeyPins ?? {},
|
|
1725
|
-
checkpointVersionPins: parsed.checkpointVersionPins ?? {},
|
|
1726
|
-
transparencyHeadPins: parsed.transparencyHeadPins ?? {}
|
|
1727
|
-
};
|
|
1728
|
-
} catch {
|
|
1729
|
-
return {
|
|
1730
|
-
version: 1,
|
|
1731
|
-
userKeyPins: {},
|
|
1732
|
-
checkpointVersionPins: {},
|
|
1733
|
-
transparencyHeadPins: {}
|
|
1734
|
-
};
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
function saveTrustStore(trustStorePath, store) {
|
|
1738
|
-
fs22.mkdirSync(path22.dirname(trustStorePath), { recursive: true });
|
|
1739
|
-
fs22.writeFileSync(trustStorePath, JSON.stringify(store, null, 2) + "\n", "utf8");
|
|
1740
|
-
}
|
|
1741
|
-
function getPinStorageKey(orgId, orgUserId) {
|
|
1742
|
-
return `${orgId}:${orgUserId}`;
|
|
1743
|
-
}
|
|
1744
|
-
function getAgentPinStorageKey(orgId, agentId) {
|
|
1745
|
-
void orgId;
|
|
1746
|
-
return `agent:${agentId}`;
|
|
1747
|
-
}
|
|
1748
|
-
function getDirectoryPinStorageKey(orgId) {
|
|
1749
|
-
return `org:${orgId}`;
|
|
1750
|
-
}
|
|
1751
|
-
function getAgentTransparencyPinStorageKey(orgId, agentId) {
|
|
1752
|
-
void orgId;
|
|
1753
|
-
return `agent-head:${agentId}`;
|
|
1754
|
-
}
|
|
1755
|
-
async function fetchWitnessArtifact(witnessBaseUrl, pathName) {
|
|
1756
|
-
const response = await fetch(
|
|
1757
|
-
buildTransparencyWitnessUrl(witnessBaseUrl, pathName),
|
|
1758
|
-
{
|
|
1759
|
-
cache: "no-store"
|
|
1760
|
-
}
|
|
1761
|
-
);
|
|
1762
|
-
if (!response.ok) {
|
|
1763
|
-
throw new Error(
|
|
1764
|
-
`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.`
|
|
1765
|
-
);
|
|
1766
|
-
}
|
|
1767
|
-
return response.json();
|
|
1768
|
-
}
|
|
1769
|
-
function getSinglePinnedTransparencyHead(trustStorePath) {
|
|
1770
|
-
const store = loadTrustStore(trustStorePath);
|
|
1771
|
-
const heads = Object.values(store.transparencyHeadPins);
|
|
1772
|
-
return heads.length === 1 ? heads[0] : null;
|
|
1773
|
-
}
|
|
1774
|
-
async function getPublicOrgWitnessHead(apiBaseUrl, orgId, configuredWitnessBaseUrl) {
|
|
1775
|
-
const witnessBaseUrl = resolveTransparencyWitnessBaseUrl({
|
|
1776
|
-
apiBaseUrl,
|
|
1777
|
-
configuredBaseUrl: configuredWitnessBaseUrl
|
|
1778
|
-
});
|
|
1779
|
-
if (!witnessBaseUrl) {
|
|
1780
|
-
return null;
|
|
1781
|
-
}
|
|
1782
|
-
const artifact = await fetchWitnessArtifact(
|
|
1783
|
-
witnessBaseUrl,
|
|
1784
|
-
buildOrgUserKeyDirectoryWitnessPath(orgId)
|
|
1785
|
-
);
|
|
1786
|
-
if (artifact.kind !== "org-user-key-directory" || artifact.orgId !== orgId || !verifyOrgUserKeyDirectoryWitnessArtifact(
|
|
1787
|
-
artifact,
|
|
1788
|
-
TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM
|
|
1789
|
-
)) {
|
|
1790
|
-
throw new Error(`Public transparency witness verification failed for org ${orgId}.`);
|
|
1791
|
-
}
|
|
1792
|
-
return artifact.head;
|
|
1793
|
-
}
|
|
1794
|
-
async function getPublicAgentWitnessHead(apiBaseUrl, agentId, configuredWitnessBaseUrl) {
|
|
1795
|
-
const witnessBaseUrl = resolveTransparencyWitnessBaseUrl({
|
|
1796
|
-
apiBaseUrl,
|
|
1797
|
-
configuredBaseUrl: configuredWitnessBaseUrl
|
|
1798
|
-
});
|
|
1799
|
-
if (!witnessBaseUrl) {
|
|
1800
|
-
return null;
|
|
1801
|
-
}
|
|
1802
|
-
const artifact = await fetchWitnessArtifact(
|
|
1803
|
-
witnessBaseUrl,
|
|
1804
|
-
buildAgentPublicKeyWitnessPath(agentId)
|
|
1805
|
-
);
|
|
1806
|
-
if (artifact.kind !== "agent-public-key" || artifact.agentId !== agentId || !verifyAgentPublicKeyWitnessArtifact(
|
|
1807
|
-
artifact,
|
|
1808
|
-
TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM
|
|
1809
|
-
)) {
|
|
1810
|
-
throw new Error(`Public transparency witness verification failed for agent ${agentId}.`);
|
|
1811
|
-
}
|
|
1812
|
-
return artifact.head;
|
|
1813
|
-
}
|
|
1814
|
-
async function pinVaultUserPublicKeys(trustStorePath, orgId, publicKeys) {
|
|
1815
|
-
const store = loadTrustStore(trustStorePath);
|
|
1816
|
-
let changed = false;
|
|
1817
|
-
for (const key of publicKeys) {
|
|
1818
|
-
const storageKey = getPinStorageKey(orgId, key.orgUserId);
|
|
1819
|
-
const computedFingerprint = getPublicKeyFingerprint(key.publicKey);
|
|
1820
|
-
if (computedFingerprint !== key.fingerprint) {
|
|
1821
|
-
throw new Error(`Server returned a mismatched fingerprint for user ${key.orgUserId}.`);
|
|
1822
|
-
}
|
|
1823
|
-
const existing = store.userKeyPins[storageKey];
|
|
1824
|
-
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1825
|
-
if (!existing) {
|
|
1826
|
-
store.userKeyPins[storageKey] = {
|
|
1827
|
-
keyPairId: key.userKeyPairId,
|
|
1828
|
-
fingerprint: key.fingerprint,
|
|
1829
|
-
publicKey: key.publicKey,
|
|
1830
|
-
pinnedAt: verifiedAt,
|
|
1831
|
-
verifiedAt
|
|
1832
|
-
};
|
|
1833
|
-
changed = true;
|
|
1834
|
-
continue;
|
|
1835
|
-
}
|
|
1836
|
-
if (existing.keyPairId === key.userKeyPairId) {
|
|
1837
|
-
if (existing.fingerprint !== key.fingerprint || existing.publicKey !== key.publicKey) {
|
|
1838
|
-
throw new Error(
|
|
1839
|
-
`Pinned public key ${key.userKeyPairId} changed unexpectedly for user ${key.orgUserId}.`
|
|
1840
|
-
);
|
|
1841
|
-
}
|
|
1842
|
-
if (existing.verifiedAt !== verifiedAt) {
|
|
1843
|
-
store.userKeyPins[storageKey] = {
|
|
1844
|
-
...existing,
|
|
1845
|
-
verifiedAt
|
|
1846
|
-
};
|
|
1847
|
-
changed = true;
|
|
1848
|
-
}
|
|
1849
|
-
continue;
|
|
1850
|
-
}
|
|
1851
|
-
if (!key.previousUserKeyPairId || key.previousUserKeyPairId !== existing.keyPairId || !key.rotationSignature) {
|
|
1852
|
-
throw new Error(`Public key rotation for user ${key.orgUserId} is missing a trusted continuity proof.`);
|
|
1853
|
-
}
|
|
1854
|
-
const rotationVerified = verifyUserKeyRotation(
|
|
1855
|
-
existing.keyPairId,
|
|
1856
|
-
key.publicKey,
|
|
1857
|
-
key.rotationSignature,
|
|
1858
|
-
existing.publicKey
|
|
1859
|
-
);
|
|
1860
|
-
if (!rotationVerified) {
|
|
1861
|
-
throw new Error(`Public key rotation for user ${key.orgUserId} failed signature verification.`);
|
|
1862
|
-
}
|
|
1863
|
-
store.userKeyPins[storageKey] = {
|
|
1864
|
-
keyPairId: key.userKeyPairId,
|
|
1865
|
-
fingerprint: key.fingerprint,
|
|
1866
|
-
publicKey: key.publicKey,
|
|
1867
|
-
pinnedAt: existing.pinnedAt,
|
|
1868
|
-
verifiedAt
|
|
1869
|
-
};
|
|
1870
|
-
changed = true;
|
|
1871
|
-
}
|
|
1872
|
-
if (changed) {
|
|
1873
|
-
saveTrustStore(trustStorePath, store);
|
|
1874
|
-
}
|
|
1875
|
-
return publicKeys;
|
|
1876
|
-
}
|
|
1877
|
-
async function pinVaultAgentPublicKeys(trustStorePath, orgId, publicKeys, apiBaseUrl, configuredWitnessBaseUrl) {
|
|
1878
|
-
const store = loadTrustStore(trustStorePath);
|
|
1879
|
-
let changed = false;
|
|
1880
|
-
for (const key of publicKeys) {
|
|
1881
|
-
const storageKey = getAgentPinStorageKey(orgId, key.agentId);
|
|
1882
|
-
const computedFingerprint = getPublicKeyFingerprint(key.publicKey);
|
|
1883
|
-
if (computedFingerprint !== key.fingerprint) {
|
|
1884
|
-
throw new Error(`Server returned a mismatched fingerprint for agent ${key.agentId}.`);
|
|
1885
|
-
}
|
|
1886
|
-
if (!key.transparency) {
|
|
1887
|
-
throw new Error(`Server omitted the agent public-key transparency proof for agent ${key.agentId}.`);
|
|
1888
|
-
}
|
|
1889
|
-
const currentProofEntry = key.transparency.entries[key.transparency.entries.length - 1];
|
|
1890
|
-
if (!currentProofEntry) {
|
|
1891
|
-
throw new Error(`Server returned an empty agent public-key transparency proof for agent ${key.agentId}.`);
|
|
1892
|
-
}
|
|
1893
|
-
const expectedCurrentEntry = buildAgentPublicKeyTransparencyEntry({
|
|
1894
|
-
agentId: key.agentId,
|
|
1895
|
-
version: currentProofEntry.version,
|
|
1896
|
-
encryptionKeyId: key.encryptionKeyId,
|
|
1897
|
-
publicKey: key.publicKey,
|
|
1898
|
-
fingerprint: key.fingerprint,
|
|
1899
|
-
previousEncryptionKeyId: key.previousEncryptionKeyId ?? null,
|
|
1900
|
-
rotationSignature: key.rotationSignature ?? null,
|
|
1901
|
-
previousEntryHash: currentProofEntry.previousEntryHash ?? null
|
|
1902
|
-
});
|
|
1903
|
-
if (expectedCurrentEntry.entryHash !== currentProofEntry.entryHash) {
|
|
1904
|
-
throw new Error(`Agent public-key transparency entry does not match the current key for agent ${key.agentId}.`);
|
|
1905
|
-
}
|
|
1906
|
-
const pinnedTransparencyHead = store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] ?? null;
|
|
1907
|
-
const witnessHead = !pinnedTransparencyHead ? await getPublicAgentWitnessHead(apiBaseUrl, key.agentId, configuredWitnessBaseUrl) : null;
|
|
1908
|
-
const trustedPreviousHead = witnessHead ?? pinnedTransparencyHead;
|
|
1909
|
-
if (witnessHead && key.transparency.head.version < witnessHead.version) {
|
|
1910
|
-
throw new Error(`Public transparency witness head is ahead of the server response for agent ${key.agentId}.`);
|
|
1911
|
-
}
|
|
1912
|
-
if (witnessHead && key.transparency.head.version === witnessHead.version) {
|
|
1913
|
-
if (key.transparency.head.hash !== witnessHead.hash) {
|
|
1914
|
-
throw new Error(`Public transparency witness head fork detected for agent ${key.agentId}.`);
|
|
1915
|
-
}
|
|
1916
|
-
if (currentProofEntry.entryHash !== witnessHead.hash || expectedCurrentEntry.entryHash !== witnessHead.hash) {
|
|
1917
|
-
throw new Error(`Agent public-key transparency witness anchor mismatch for agent ${key.agentId}.`);
|
|
1918
|
-
}
|
|
1919
|
-
} else if (!verifyAgentPublicKeyTransparencyProof({
|
|
1920
|
-
currentEntry: expectedCurrentEntry,
|
|
1921
|
-
proof: key.transparency,
|
|
1922
|
-
previousHead: trustedPreviousHead
|
|
1923
|
-
})) {
|
|
1924
|
-
throw new Error(`Agent public-key transparency proof verification failed for agent ${key.agentId}.`);
|
|
1925
|
-
}
|
|
1926
|
-
const existing = store.userKeyPins[storageKey];
|
|
1927
|
-
const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1928
|
-
if (!existing) {
|
|
1929
|
-
store.userKeyPins[storageKey] = {
|
|
1930
|
-
keyPairId: key.encryptionKeyId,
|
|
1931
|
-
fingerprint: key.fingerprint,
|
|
1932
|
-
publicKey: key.publicKey,
|
|
1933
|
-
pinnedAt: verifiedAt,
|
|
1934
|
-
verifiedAt
|
|
1935
|
-
};
|
|
1936
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1937
|
-
changed = true;
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
if (existing.keyPairId === key.encryptionKeyId) {
|
|
1941
|
-
if (existing.fingerprint !== key.fingerprint || existing.publicKey !== key.publicKey) {
|
|
1942
|
-
throw new Error(
|
|
1943
|
-
`Pinned public key ${key.encryptionKeyId} changed unexpectedly for agent ${key.agentId}.`
|
|
1944
|
-
);
|
|
1945
|
-
}
|
|
1946
|
-
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) {
|
|
1947
|
-
store.userKeyPins[storageKey] = {
|
|
1948
|
-
...existing,
|
|
1949
|
-
verifiedAt
|
|
1950
|
-
};
|
|
1951
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1952
|
-
changed = true;
|
|
1953
|
-
}
|
|
1954
|
-
continue;
|
|
1955
|
-
}
|
|
1956
|
-
if (!key.previousEncryptionKeyId || key.previousEncryptionKeyId !== existing.keyPairId || !key.rotationSignature) {
|
|
1957
|
-
throw new Error(`Public key rotation for agent ${key.agentId} is missing a trusted continuity proof.`);
|
|
1958
|
-
}
|
|
1959
|
-
const rotationVerified = verifyUserKeyRotation(
|
|
1960
|
-
existing.keyPairId,
|
|
1961
|
-
key.publicKey,
|
|
1962
|
-
key.rotationSignature,
|
|
1963
|
-
existing.publicKey
|
|
1964
|
-
);
|
|
1965
|
-
if (!rotationVerified) {
|
|
1966
|
-
throw new Error(`Public key rotation for agent ${key.agentId} failed signature verification.`);
|
|
1967
|
-
}
|
|
1968
|
-
store.userKeyPins[storageKey] = {
|
|
1969
|
-
keyPairId: key.encryptionKeyId,
|
|
1970
|
-
fingerprint: key.fingerprint,
|
|
1971
|
-
publicKey: key.publicKey,
|
|
1972
|
-
pinnedAt: existing.pinnedAt,
|
|
1973
|
-
verifiedAt
|
|
1974
|
-
};
|
|
1975
|
-
store.transparencyHeadPins[getAgentTransparencyPinStorageKey(orgId, key.agentId)] = key.transparency.head;
|
|
1976
|
-
changed = true;
|
|
1977
|
-
}
|
|
1978
|
-
if (changed) {
|
|
1979
|
-
saveTrustStore(trustStorePath, store);
|
|
1980
|
-
}
|
|
1981
|
-
return publicKeys;
|
|
1982
|
-
}
|
|
1983
|
-
async function verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead) {
|
|
1984
|
-
if (!directory.directoryCheckpoint) {
|
|
1985
|
-
if (directory.publicKeys.length > 0) {
|
|
1986
|
-
throw new Error("Server omitted the user-key directory checkpoint for a non-empty vault signer directory.");
|
|
1987
|
-
}
|
|
1988
|
-
return null;
|
|
1989
|
-
}
|
|
1990
|
-
const { directoryCheckpoint } = directory;
|
|
1991
|
-
const orgId = directoryCheckpoint.checkpoint.orgId;
|
|
1992
|
-
if (!directoryCheckpoint.signerOrgUserId || !directoryCheckpoint.signerPublicKey) {
|
|
1993
|
-
throw new Error("Server returned an incomplete user-key directory signer payload.");
|
|
1994
|
-
}
|
|
1995
|
-
const signerFingerprint = getPublicKeyFingerprint(directoryCheckpoint.signerPublicKey);
|
|
1996
|
-
const signerEntry = directoryCheckpoint.checkpoint.entries.find(
|
|
1997
|
-
(entry) => entry.userKeyPairId === directoryCheckpoint.signerUserKeyPairId && entry.orgUserId === directoryCheckpoint.signerOrgUserId
|
|
1998
|
-
);
|
|
1999
|
-
const signerKey = {
|
|
2000
|
-
userKeyPairId: directoryCheckpoint.signerUserKeyPairId,
|
|
2001
|
-
orgUserId: directoryCheckpoint.signerOrgUserId,
|
|
2002
|
-
publicKey: directoryCheckpoint.signerPublicKey,
|
|
2003
|
-
fingerprint: signerFingerprint,
|
|
2004
|
-
previousUserKeyPairId: signerEntry?.previousUserKeyPairId ?? null,
|
|
2005
|
-
rotationSignature: signerEntry?.rotationSignature ?? null
|
|
2006
|
-
};
|
|
2007
|
-
const storeBeforeVerification = loadTrustStore(trustStorePath);
|
|
2008
|
-
try {
|
|
2009
|
-
await pinVaultUserPublicKeys(trustStorePath, orgId, [signerKey]);
|
|
2010
|
-
const verified = verifyUserKeyDirectoryCheckpoint(
|
|
2011
|
-
directoryCheckpoint.checkpoint,
|
|
2012
|
-
directoryCheckpoint.signature,
|
|
2013
|
-
directoryCheckpoint.signerPublicKey
|
|
2014
|
-
);
|
|
2015
|
-
if (!verified) {
|
|
2016
|
-
throw new Error(`User-key directory signature verification failed for org ${orgId}.`);
|
|
2017
|
-
}
|
|
2018
|
-
if (!directory.transparency) {
|
|
2019
|
-
throw new Error(`Server omitted the user-key directory transparency proof for org ${orgId}.`);
|
|
2020
|
-
}
|
|
2021
|
-
const store = loadTrustStore(trustStorePath);
|
|
2022
|
-
const pinnedTransparencyHead = store.transparencyHeadPins[getDirectoryPinStorageKey(orgId)] ?? null;
|
|
2023
|
-
const trustedPreviousHead = anchorHead ?? pinnedTransparencyHead;
|
|
2024
|
-
const legacyPinnedVersion = store.checkpointVersionPins[getDirectoryPinStorageKey(orgId)] ?? null;
|
|
2025
|
-
if (!trustedPreviousHead && legacyPinnedVersion !== null && directory.transparency.head.version < legacyPinnedVersion) {
|
|
2026
|
-
throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
|
|
2027
|
-
}
|
|
2028
|
-
if (!trustedPreviousHead && directory.transparency.entries.length === 0) {
|
|
2029
|
-
throw new Error(`Server omitted the current transparency entry for org ${orgId}.`);
|
|
2030
|
-
}
|
|
2031
|
-
if (directory.transparency.entries.length > 0) {
|
|
2032
|
-
const currentProofEntry = directory.transparency.entries[directory.transparency.entries.length - 1];
|
|
2033
|
-
if (!currentProofEntry) {
|
|
2034
|
-
throw new Error(`Server returned an empty transparency proof for org ${orgId}.`);
|
|
2035
|
-
}
|
|
2036
|
-
const expectedCurrentEntry = buildUserKeyDirectoryTransparencyEntry({
|
|
2037
|
-
checkpoint: directoryCheckpoint.checkpoint,
|
|
2038
|
-
signerUserKeyPairId: directoryCheckpoint.signerUserKeyPairId,
|
|
2039
|
-
signerOrgUserId: directoryCheckpoint.signerOrgUserId,
|
|
2040
|
-
signerPublicKey: directoryCheckpoint.signerPublicKey,
|
|
2041
|
-
signature: directoryCheckpoint.signature,
|
|
2042
|
-
previousEntryHash: currentProofEntry.previousEntryHash ?? null
|
|
2043
|
-
});
|
|
2044
|
-
if (expectedCurrentEntry.entryHash !== currentProofEntry.entryHash) {
|
|
2045
|
-
throw new Error(`User-key transparency entry does not match the signed directory for org ${orgId}.`);
|
|
2046
|
-
}
|
|
2047
|
-
if (anchorHead && directory.transparency.head.version === anchorHead.version) {
|
|
2048
|
-
if (directory.transparency.head.hash !== anchorHead.hash) {
|
|
2049
|
-
throw new Error(`Public transparency witness head fork detected for org ${orgId}.`);
|
|
2050
|
-
}
|
|
2051
|
-
if (currentProofEntry.entryHash !== anchorHead.hash || expectedCurrentEntry.entryHash !== anchorHead.hash) {
|
|
2052
|
-
throw new Error(`User-key transparency witness anchor mismatch for org ${orgId}.`);
|
|
2053
|
-
}
|
|
2054
|
-
} else if (!verifyUserKeyDirectoryTransparencyProof({
|
|
2055
|
-
currentEntry: expectedCurrentEntry,
|
|
2056
|
-
proof: directory.transparency,
|
|
2057
|
-
previousHead: trustedPreviousHead
|
|
2058
|
-
})) {
|
|
2059
|
-
throw new Error(`User-key transparency proof verification failed for org ${orgId}.`);
|
|
2060
|
-
}
|
|
2061
|
-
} else if (anchorHead && directory.transparency.head.version === anchorHead.version) {
|
|
2062
|
-
throw new Error(`Server omitted the current transparency entry required to verify org ${orgId} against the public witness.`);
|
|
2063
|
-
} else if (!trustedPreviousHead || trustedPreviousHead.version !== directory.transparency.head.version || trustedPreviousHead.hash !== directory.transparency.head.hash) {
|
|
2064
|
-
throw new Error(`Server returned an incomplete user-key transparency proof for org ${orgId}.`);
|
|
2065
|
-
}
|
|
2066
|
-
assertAndPinTransparencyHead(trustStorePath, orgId, directory.transparency.head);
|
|
2067
|
-
const checkpointEntries = new Map(
|
|
2068
|
-
directoryCheckpoint.checkpoint.entries.map((entry) => [entry.userKeyPairId, entry])
|
|
2069
|
-
);
|
|
2070
|
-
for (const key of directory.publicKeys) {
|
|
2071
|
-
const entry = checkpointEntries.get(key.userKeyPairId);
|
|
2072
|
-
if (!entry) {
|
|
2073
|
-
throw new Error(`User key ${key.userKeyPairId} is missing from the signed org directory.`);
|
|
2074
|
-
}
|
|
2075
|
-
if (entry.orgUserId !== key.orgUserId || entry.fingerprint !== key.fingerprint || entry.previousUserKeyPairId !== key.previousUserKeyPairId || entry.rotationSignature !== key.rotationSignature) {
|
|
2076
|
-
throw new Error(`User key ${key.userKeyPairId} does not match the signed org directory.`);
|
|
2077
|
-
}
|
|
2078
|
-
}
|
|
2079
|
-
return orgId;
|
|
2080
|
-
} catch (error) {
|
|
2081
|
-
saveTrustStore(trustStorePath, storeBeforeVerification);
|
|
2082
|
-
throw error;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
async function verifyAndPinVaultUserPublicKeys(trustStorePath, directory, anchorHead) {
|
|
2086
|
-
const orgId = await verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead);
|
|
2087
|
-
if (!orgId) {
|
|
2088
|
-
return directory.publicKeys;
|
|
2089
|
-
}
|
|
2090
|
-
return pinVaultUserPublicKeys(trustStorePath, orgId, directory.publicKeys);
|
|
2091
|
-
}
|
|
2092
|
-
async function verifyAndPinVaultAgentPublicKeys(trustStorePath, orgId, publicKeys, apiBaseUrl, configuredWitnessBaseUrl) {
|
|
2093
|
-
return pinVaultAgentPublicKeys(
|
|
2094
|
-
trustStorePath,
|
|
2095
|
-
orgId,
|
|
2096
|
-
publicKeys,
|
|
2097
|
-
apiBaseUrl,
|
|
2098
|
-
configuredWitnessBaseUrl
|
|
2099
|
-
);
|
|
2100
|
-
}
|
|
2101
|
-
function assertAndPinCheckpointVersion(trustStorePath, storageKey, version) {
|
|
2102
|
-
const store = loadTrustStore(trustStorePath);
|
|
2103
|
-
const pinnedVersion = store.checkpointVersionPins[storageKey];
|
|
2104
|
-
if (pinnedVersion !== void 0 && version < pinnedVersion) {
|
|
2105
|
-
throw new Error(`Checkpoint version rolled back unexpectedly for ${storageKey}.`);
|
|
2106
|
-
}
|
|
2107
|
-
if (pinnedVersion === void 0 || version > pinnedVersion) {
|
|
2108
|
-
store.checkpointVersionPins[storageKey] = version;
|
|
2109
|
-
saveTrustStore(trustStorePath, store);
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
function assertAndPinTransparencyHead(trustStorePath, orgId, head) {
|
|
2113
|
-
const store = loadTrustStore(trustStorePath);
|
|
2114
|
-
const storageKey = getDirectoryPinStorageKey(orgId);
|
|
2115
|
-
const pinnedHead = store.transparencyHeadPins[storageKey];
|
|
2116
|
-
if (pinnedHead) {
|
|
2117
|
-
if (head.version < pinnedHead.version) {
|
|
2118
|
-
throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
|
|
2119
|
-
}
|
|
2120
|
-
if (head.version === pinnedHead.version && head.hash !== pinnedHead.hash) {
|
|
2121
|
-
throw new Error(`User-key transparency head fork detected for org ${orgId}.`);
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
if (!pinnedHead || head.version > pinnedHead.version) {
|
|
2125
|
-
store.transparencyHeadPins[storageKey] = head;
|
|
2126
|
-
saveTrustStore(trustStorePath, store);
|
|
2127
|
-
}
|
|
2128
|
-
assertAndPinCheckpointVersion(trustStorePath, storageKey, head.version);
|
|
2129
|
-
}
|
|
2130
|
-
var R4_DEFAULT_API_BASE_URL2 = "https://r4.dev";
|
|
2131
|
-
var R4_DEV_API_BASE_URL2 = "https://dev.r4.dev";
|
|
2132
|
-
function toScreamingSnakeCase(input) {
|
|
2133
|
-
return input.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
|
|
2134
|
-
}
|
|
2135
|
-
function resolveTrustStorePath2(config) {
|
|
2136
|
-
if (config.trustStorePath) {
|
|
2137
|
-
return path3.resolve(config.trustStorePath);
|
|
2138
|
-
}
|
|
2139
|
-
if (config.privateKeyPath) {
|
|
2140
|
-
return `${path3.resolve(config.privateKeyPath)}.trust.json`;
|
|
2141
|
-
}
|
|
2142
|
-
return path3.resolve(process.cwd(), ".r4-trust-store.json");
|
|
2143
|
-
}
|
|
2144
|
-
function resolveApiBaseUrl(config) {
|
|
2145
|
-
if (config.baseUrl) {
|
|
2146
|
-
return config.baseUrl;
|
|
2147
|
-
}
|
|
2148
|
-
return config.dev ? R4_DEV_API_BASE_URL2 : R4_DEFAULT_API_BASE_URL2;
|
|
2149
|
-
}
|
|
2150
|
-
function buildVaultSummaryCheckpointFromListResponse(response, version) {
|
|
2151
|
-
return {
|
|
2152
|
-
vaultId: response.vaultId,
|
|
2153
|
-
version,
|
|
2154
|
-
name: response.vaultName,
|
|
2155
|
-
dataClassification: response.dataClassification ?? null,
|
|
2156
|
-
currentDekVersion: response.currentDekVersion ?? null,
|
|
2157
|
-
items: response.items.map((item) => ({
|
|
2158
|
-
id: item.id,
|
|
2159
|
-
name: item.name,
|
|
2160
|
-
type: item.type ?? null,
|
|
2161
|
-
websites: item.websites ?? [],
|
|
2162
|
-
groupId: item.groupId ?? null
|
|
2163
|
-
})),
|
|
2164
|
-
groups: response.vaultItemGroups.map((group) => ({
|
|
2165
|
-
id: group.id,
|
|
2166
|
-
name: group.name,
|
|
2167
|
-
parentId: group.parentId ?? null
|
|
2168
|
-
}))
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
function buildVaultItemDetailCheckpointFromResponse(item, version) {
|
|
2172
|
-
return {
|
|
2173
|
-
vaultItemId: item.id,
|
|
2174
|
-
vaultId: item.vaultId,
|
|
2175
|
-
version,
|
|
2176
|
-
name: item.name,
|
|
2177
|
-
type: item.type ?? null,
|
|
2178
|
-
websites: item.websites ?? [],
|
|
2179
|
-
groupId: item.groupId ?? null,
|
|
2180
|
-
fields: item.fields.map((field, index) => ({
|
|
2181
|
-
id: field.id,
|
|
2182
|
-
name: field.name,
|
|
2183
|
-
type: field.type,
|
|
2184
|
-
order: field.order ?? index,
|
|
2185
|
-
fieldInstanceIds: field.fieldInstanceIds ?? [],
|
|
2186
|
-
assetIds: field.assetIds ?? []
|
|
2187
|
-
}))
|
|
2188
|
-
};
|
|
2189
|
-
}
|
|
2190
|
-
function getCheckpointSignerPublicKey(params) {
|
|
2191
|
-
const signerType = params.checkpoint.signerType ?? "USER_KEY_PAIR";
|
|
2192
|
-
if (signerType === "AGENT_ENCRYPTION_KEY") {
|
|
2193
|
-
const signerKey2 = params.agentKeys.find(
|
|
2194
|
-
(publicKey) => publicKey.encryptionKeyId === params.checkpoint.signerEncryptionKeyId
|
|
2195
|
-
);
|
|
2196
|
-
const historicalSignerKey = params.agentKeys.flatMap((publicKey) => publicKey.transparency?.entries ?? []).find((entry) => entry.encryptionKeyId === params.checkpoint.signerEncryptionKeyId);
|
|
2197
|
-
if (!signerKey2 && !historicalSignerKey) {
|
|
2198
|
-
throw new Error(
|
|
2199
|
-
`R4 SDK: checkpoint signer ${params.checkpoint.signerEncryptionKeyId ?? "unknown"} was not present in the trusted agent key directory.`
|
|
2200
|
-
);
|
|
2201
|
-
}
|
|
2202
|
-
return signerKey2?.publicKey ?? historicalSignerKey.publicKey;
|
|
2203
|
-
}
|
|
2204
|
-
const signerKey = params.userKeys.find(
|
|
2205
|
-
(publicKey) => publicKey.userKeyPairId === params.checkpoint.signerUserKeyPairId
|
|
2206
|
-
);
|
|
2207
|
-
if (!signerKey) {
|
|
2208
|
-
throw new Error(
|
|
2209
|
-
`R4 SDK: checkpoint signer ${params.checkpoint.signerUserKeyPairId ?? "unknown"} was not present in the trusted user key directory.`
|
|
2210
|
-
);
|
|
2211
|
-
}
|
|
2212
|
-
return signerKey.publicKey;
|
|
2213
|
-
}
|
|
2214
|
-
var R4 = class _R4 {
|
|
2215
|
-
client;
|
|
2216
|
-
baseUrl;
|
|
2217
|
-
transparencyWitnessUrl;
|
|
2218
|
-
projectId;
|
|
2219
|
-
privateKeyPem;
|
|
2220
|
-
publicKeyPem;
|
|
2221
|
-
trustStorePath;
|
|
2222
|
-
_env = null;
|
|
2223
|
-
constructor(config) {
|
|
2224
|
-
if (!config.apiKey) {
|
|
2225
|
-
throw new Error("R4 SDK: apiKey is required");
|
|
2226
|
-
}
|
|
2227
|
-
if (!config.privateKey && !config.privateKeyPath) {
|
|
2228
|
-
throw new Error(
|
|
2229
|
-
"R4 SDK: privateKey or privateKeyPath is required for zero-trust local decryption."
|
|
2230
|
-
);
|
|
2231
|
-
}
|
|
2232
|
-
const baseUrl = resolveApiBaseUrl(config);
|
|
2233
|
-
this.baseUrl = baseUrl;
|
|
2234
|
-
this.transparencyWitnessUrl = config.transparencyWitnessUrl;
|
|
2235
|
-
this.client = new R4Client(config.apiKey, baseUrl);
|
|
2236
|
-
this.projectId = config.projectId;
|
|
2237
|
-
this.privateKeyPem = config.privateKey ?? loadPrivateKey2(config.privateKeyPath);
|
|
2238
|
-
this.publicKeyPem = derivePublicKey2(this.privateKeyPem);
|
|
2239
|
-
this.trustStorePath = resolveTrustStorePath2(config);
|
|
2240
|
-
}
|
|
2241
|
-
/**
|
|
2242
|
-
* Static factory method that creates and initializes an R4 instance.
|
|
2243
|
-
*/
|
|
2244
|
-
static async create(config) {
|
|
2245
|
-
const instance = new _R4(config);
|
|
2246
|
-
await instance.init();
|
|
2247
|
-
return instance;
|
|
2248
|
-
}
|
|
2249
|
-
/**
|
|
2250
|
-
* Initializes the SDK by registering the agent public key (idempotent) and
|
|
2251
|
-
* decrypting all accessible vault values locally into a flat env map.
|
|
2252
|
-
*/
|
|
2253
|
-
async init() {
|
|
2254
|
-
this._env = await this.fetchEnv();
|
|
2255
|
-
}
|
|
2256
|
-
/**
|
|
2257
|
-
* Returns the locally decrypted env map.
|
|
2258
|
-
*/
|
|
2259
|
-
get env() {
|
|
2260
|
-
if (!this._env) {
|
|
2261
|
-
throw new Error(
|
|
2262
|
-
"R4 SDK: env is not initialized. Call await r4.init() first, or use R4.create() for automatic initialization."
|
|
2263
|
-
);
|
|
2264
|
-
}
|
|
2265
|
-
return this._env;
|
|
2266
|
-
}
|
|
2267
|
-
/**
|
|
2268
|
-
* Re-fetches and locally re-decrypts the current vault view.
|
|
2269
|
-
*/
|
|
2270
|
-
async refresh() {
|
|
2271
|
-
this._env = await this.fetchEnv();
|
|
2272
|
-
}
|
|
2273
|
-
/**
|
|
2274
|
-
* Sends an authenticated request to an arbitrary machine API route so
|
|
2275
|
-
* callers can use the full headless surface without hand-rolling fetch code.
|
|
2276
|
-
*/
|
|
2277
|
-
async requestMachine(params) {
|
|
2278
|
-
return this.client.requestMachine(params);
|
|
2279
|
-
}
|
|
2280
|
-
/**
|
|
2281
|
-
* Fetches, verifies, and locally decrypts a token provider's vault-backed
|
|
2282
|
-
* credential fields without exposing plaintext back to R4.
|
|
2283
|
-
*/
|
|
2284
|
-
async getTokenProviderFields(tokenProviderId, options) {
|
|
2285
|
-
if (options?.unsafeExport !== true) {
|
|
2286
|
-
throw new Error(
|
|
2287
|
-
"R4 SDK: getTokenProviderFields exports decrypted provider secrets. Pass { unsafeExport: true } to confirm or use callAnthropicWithTokenProvider() instead."
|
|
2288
|
-
);
|
|
2289
|
-
}
|
|
2290
|
-
await this.registerAgentPublicKey();
|
|
2291
|
-
const runtime = await this.getVerifiedTokenProviderRuntime(tokenProviderId);
|
|
2292
|
-
return {
|
|
2293
|
-
id: runtime.id,
|
|
2294
|
-
name: runtime.name,
|
|
2295
|
-
providerType: runtime.providerType,
|
|
2296
|
-
fields: this.decryptTokenProviderFields(runtime)
|
|
2297
|
-
};
|
|
2298
|
-
}
|
|
2299
|
-
/**
|
|
2300
|
-
* Managed Anthropic call path:
|
|
2301
|
-
* 1. decrypt the provider secret locally
|
|
2302
|
-
* 2. preflight the budget gate with estimated usage
|
|
2303
|
-
* 3. call Anthropic directly
|
|
2304
|
-
* 4. publish finalized usage back to R4
|
|
2305
|
-
*/
|
|
2306
|
-
async callAnthropicWithTokenProvider(params) {
|
|
2307
|
-
await this.registerAgentPublicKey();
|
|
2308
|
-
const runtime = await this.getVerifiedTokenProviderRuntime(params.tokenProviderId);
|
|
2309
|
-
if (runtime.providerType !== "ANTHROPIC") {
|
|
2310
|
-
throw new Error(
|
|
2311
|
-
`R4 SDK: token provider ${params.tokenProviderId} is ${runtime.providerType}, not ANTHROPIC.`
|
|
2312
|
-
);
|
|
2313
|
-
}
|
|
2314
|
-
const fields = this.decryptTokenProviderFields(runtime);
|
|
2315
|
-
const apiKey = fields["API Key"];
|
|
2316
|
-
const endpoint = fields["Endpoint URL"] || "https://api.anthropic.com/v1/messages";
|
|
2317
|
-
const model = this.requireStringField(params.body.model, "body.model");
|
|
2318
|
-
if (!apiKey) {
|
|
2319
|
-
throw new Error(`R4 SDK: token provider ${params.tokenProviderId} does not contain an API Key field.`);
|
|
2320
|
-
}
|
|
2321
|
-
const requestId = randomUUID();
|
|
2322
|
-
const authorization = await this.client.authorizeTokenProviderUsage(params.tokenProviderId, {
|
|
2323
|
-
model,
|
|
2324
|
-
estimatedInputTokens: params.estimatedInputTokens ?? 0,
|
|
2325
|
-
estimatedOutputTokens: params.estimatedOutputTokens ?? this.inferEstimatedOutputTokens(params.body.max_tokens)
|
|
2326
|
-
});
|
|
2327
|
-
const response = await fetch(endpoint, {
|
|
2328
|
-
method: "POST",
|
|
2329
|
-
headers: {
|
|
2330
|
-
"Content-Type": "application/json",
|
|
2331
|
-
"x-api-key": apiKey,
|
|
2332
|
-
"anthropic-version": "2023-06-01"
|
|
2333
|
-
},
|
|
2334
|
-
body: JSON.stringify(params.body),
|
|
2335
|
-
signal: params.signal
|
|
2336
|
-
});
|
|
2337
|
-
if (!response.ok) {
|
|
2338
|
-
const bodyText = await response.text().catch(() => "");
|
|
2339
|
-
throw new Error(`Anthropic API error: ${response.status} ${response.statusText}${bodyText ? ` - ${bodyText}` : ""}`);
|
|
2340
|
-
}
|
|
2341
|
-
const parsedResponse = await response.json();
|
|
2342
|
-
const inputTokens = this.toNonNegativeInt(parsedResponse.usage?.input_tokens);
|
|
2343
|
-
const outputTokens = this.toNonNegativeInt(parsedResponse.usage?.output_tokens);
|
|
2344
|
-
const usage = await this.client.reportTokenProviderUsage(params.tokenProviderId, {
|
|
2345
|
-
requestId,
|
|
2346
|
-
model,
|
|
2347
|
-
vendorRequestId: typeof parsedResponse.id === "string" ? parsedResponse.id : null,
|
|
2348
|
-
inputTokens,
|
|
2349
|
-
outputTokens,
|
|
2350
|
-
totalTokens: inputTokens + outputTokens,
|
|
2351
|
-
estimatedCostUsd: authorization.estimatedCostUsd,
|
|
2352
|
-
metadata: {
|
|
2353
|
-
providerType: runtime.providerType
|
|
2354
|
-
}
|
|
2355
|
-
});
|
|
2356
|
-
return {
|
|
2357
|
-
response: parsedResponse,
|
|
2358
|
-
usage,
|
|
2359
|
-
authorization,
|
|
2360
|
-
requestId
|
|
2361
|
-
};
|
|
2362
|
-
}
|
|
2363
|
-
/**
|
|
2364
|
-
* Registers the local agent public key, loads all accessible vaults, verifies
|
|
2365
|
-
* wrapped-DEK signatures against pinned signer keys, unwraps each vault DEK,
|
|
2366
|
-
* and builds a flat SCREAMING_SNAKE_CASE env map from decrypted field values.
|
|
2367
|
-
*/
|
|
2368
|
-
async fetchEnv() {
|
|
2369
|
-
await this.registerAgentPublicKey();
|
|
2370
|
-
const { vaults } = await this.client.listVaults(this.projectId);
|
|
2371
|
-
const envEntries = await Promise.all(vaults.map((vault) => this.fetchVaultEnv(vault.id)));
|
|
2372
|
-
return Object.assign({}, ...envEntries);
|
|
2373
|
-
}
|
|
2374
|
-
/**
|
|
2375
|
-
* Ensures the local agent public key is registered before any zero-trust
|
|
2376
|
-
* wrapped-key or token-provider flow runs.
|
|
2377
|
-
*/
|
|
2378
|
-
async registerAgentPublicKey() {
|
|
2379
|
-
try {
|
|
2380
|
-
await this.client.registerAgentPublicKey({
|
|
2381
|
-
publicKey: this.publicKeyPem
|
|
2382
|
-
});
|
|
2383
|
-
} catch (error) {
|
|
2384
|
-
throw new Error(
|
|
2385
|
-
`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)}`
|
|
2386
|
-
);
|
|
2387
|
-
}
|
|
2388
|
-
}
|
|
2389
|
-
/**
|
|
2390
|
-
* Fetches a single vault's wrapped DEK, verifies it against the pinned signer
|
|
2391
|
-
* directory, unwraps the DEK locally, then decrypts every field value in that
|
|
2392
|
-
* vault item-by-item.
|
|
2393
|
-
*/
|
|
2394
|
-
async fetchVaultEnv(vaultId) {
|
|
2395
|
-
const pinnedTransparencyHead = getSinglePinnedTransparencyHead(this.trustStorePath);
|
|
2396
|
-
const [wrappedKey, itemsResponse, initialPublicKeyDirectory] = await Promise.all([
|
|
2397
|
-
this.client.getAgentWrappedKey(vaultId),
|
|
2398
|
-
this.client.listVaultItems(vaultId),
|
|
2399
|
-
this.client.getVaultUserKeyDirectory(
|
|
2400
|
-
vaultId,
|
|
2401
|
-
pinnedTransparencyHead ? {
|
|
2402
|
-
knownTransparencyVersion: pinnedTransparencyHead.version,
|
|
2403
|
-
knownTransparencyHash: pinnedTransparencyHead.hash
|
|
2404
|
-
} : void 0
|
|
2405
|
-
)
|
|
2406
|
-
]);
|
|
2407
|
-
let publicKeyDirectory = initialPublicKeyDirectory;
|
|
2408
|
-
let witnessAnchorHead = null;
|
|
2409
|
-
if (!pinnedTransparencyHead) {
|
|
2410
|
-
const orgId = initialPublicKeyDirectory.directoryCheckpoint?.checkpoint.orgId ?? null;
|
|
2411
|
-
if (orgId && initialPublicKeyDirectory.directoryCheckpoint && initialPublicKeyDirectory.transparency) {
|
|
2412
|
-
witnessAnchorHead = await getPublicOrgWitnessHead(
|
|
2413
|
-
this.baseUrl,
|
|
2414
|
-
orgId,
|
|
2415
|
-
this.transparencyWitnessUrl
|
|
2416
|
-
);
|
|
2417
|
-
if (witnessAnchorHead) {
|
|
2418
|
-
if (initialPublicKeyDirectory.transparency.head.version < witnessAnchorHead.version) {
|
|
2419
|
-
throw new Error(`R4 SDK: public transparency witness head is ahead of the server response for org ${orgId}.`);
|
|
2420
|
-
}
|
|
2421
|
-
if (initialPublicKeyDirectory.transparency.head.version === witnessAnchorHead.version) {
|
|
2422
|
-
if (initialPublicKeyDirectory.transparency.head.hash !== witnessAnchorHead.hash) {
|
|
2423
|
-
throw new Error(`R4 SDK: public transparency witness head fork detected for org ${orgId}.`);
|
|
2424
|
-
}
|
|
2425
|
-
} else {
|
|
2426
|
-
publicKeyDirectory = await this.client.getVaultUserKeyDirectory(vaultId, {
|
|
2427
|
-
knownTransparencyVersion: witnessAnchorHead.version,
|
|
2428
|
-
knownTransparencyHash: witnessAnchorHead.hash
|
|
2429
|
-
});
|
|
2430
|
-
}
|
|
928
|
+
const response = await client.createAgent(body);
|
|
929
|
+
spinner.stop();
|
|
930
|
+
if (globalOpts.json) {
|
|
931
|
+
console.log(JSON.stringify(response, null, 2));
|
|
932
|
+
return;
|
|
2431
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
|
+
);
|
|
2432
944
|
}
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
)?.publicKey ?? historicalAgentSignerKey?.publicKey : signerKey?.publicKey;
|
|
2454
|
-
if (!wrappedKeySignerPublicKey) {
|
|
2455
|
-
throw new Error(
|
|
2456
|
-
`R4 SDK: wrapped DEK signer ${wrappedKey.signerEncryptionKeyId ?? wrappedKey.signerUserKeyPairId ?? "unknown"} was not present in the trusted key directory.`
|
|
2457
|
-
);
|
|
2458
|
-
}
|
|
2459
|
-
const signatureVerified = verifyWrappedDekSignature(
|
|
2460
|
-
vaultId,
|
|
2461
|
-
wrappedKey.encryptionKeyId,
|
|
2462
|
-
wrappedKey.signerEncryptionKeyId ?? wrappedKey.signerUserKeyPairId,
|
|
2463
|
-
wrappedKey.dekVersion,
|
|
2464
|
-
wrappedKey.wrappedDek,
|
|
2465
|
-
wrappedKey.wrappedDekSignature,
|
|
2466
|
-
wrappedKeySignerPublicKey
|
|
2467
|
-
);
|
|
2468
|
-
if (!signatureVerified) {
|
|
2469
|
-
throw new Error(`R4 SDK: wrapped DEK signature verification failed for vault ${vaultId}.`);
|
|
2470
|
-
}
|
|
2471
|
-
const dek = unwrapDEKWithPrivateKey(wrappedKey.wrappedDek, this.privateKeyPem);
|
|
2472
|
-
if (!itemsResponse.summaryCheckpoint) {
|
|
2473
|
-
throw new Error(`R4 SDK: vault ${vaultId} is missing a signed summary checkpoint.`);
|
|
2474
|
-
}
|
|
2475
|
-
const expectedSummaryCheckpoint = buildVaultSummaryCheckpointFromListResponse(
|
|
2476
|
-
itemsResponse,
|
|
2477
|
-
itemsResponse.summaryCheckpoint.checkpoint.version
|
|
2478
|
-
);
|
|
2479
|
-
const summaryVerified = verifyVaultSummaryCheckpoint(
|
|
2480
|
-
expectedSummaryCheckpoint,
|
|
2481
|
-
itemsResponse.summaryCheckpoint.signature,
|
|
2482
|
-
getCheckpointSignerPublicKey({
|
|
2483
|
-
checkpoint: itemsResponse.summaryCheckpoint,
|
|
2484
|
-
userKeys: trustedPublicKeys,
|
|
2485
|
-
agentKeys: trustedAgentPublicKeys
|
|
2486
|
-
})
|
|
2487
|
-
);
|
|
2488
|
-
if (!summaryVerified) {
|
|
2489
|
-
throw new Error(`R4 SDK: vault summary checkpoint verification failed for vault ${vaultId}.`);
|
|
2490
|
-
}
|
|
2491
|
-
assertAndPinCheckpointVersion(
|
|
2492
|
-
this.trustStorePath,
|
|
2493
|
-
`summary:${vaultId}`,
|
|
2494
|
-
expectedSummaryCheckpoint.version
|
|
2495
|
-
);
|
|
2496
|
-
const itemDetails = await Promise.all(
|
|
2497
|
-
itemsResponse.items.map((item) => this.client.getVaultItemDetail(vaultId, item.id))
|
|
2498
|
-
);
|
|
2499
|
-
const env = {};
|
|
2500
|
-
for (const item of itemDetails) {
|
|
2501
|
-
if (!item.detailCheckpoint) {
|
|
2502
|
-
throw new Error(`R4 SDK: vault item ${item.id} is missing a signed detail checkpoint.`);
|
|
2503
|
-
}
|
|
2504
|
-
const expectedDetailCheckpoint = buildVaultItemDetailCheckpointFromResponse(
|
|
2505
|
-
item,
|
|
2506
|
-
item.detailCheckpoint.checkpoint.version
|
|
2507
|
-
);
|
|
2508
|
-
const detailVerified = verifyVaultItemDetailCheckpoint(
|
|
2509
|
-
expectedDetailCheckpoint,
|
|
2510
|
-
item.detailCheckpoint.signature,
|
|
2511
|
-
getCheckpointSignerPublicKey({
|
|
2512
|
-
checkpoint: item.detailCheckpoint,
|
|
2513
|
-
userKeys: trustedPublicKeys,
|
|
2514
|
-
agentKeys: trustedAgentPublicKeys
|
|
2515
|
-
})
|
|
2516
|
-
);
|
|
2517
|
-
if (!detailVerified) {
|
|
2518
|
-
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;
|
|
2519
965
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
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
|
|
2524
982
|
);
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
return env;
|
|
2536
|
-
}
|
|
2537
|
-
async getVerifiedTokenProviderRuntime(tokenProviderId) {
|
|
2538
|
-
const pinnedTransparencyHead = getSinglePinnedTransparencyHead(this.trustStorePath);
|
|
2539
|
-
let runtime = await this.client.getTokenProviderRuntime(
|
|
2540
|
-
tokenProviderId,
|
|
2541
|
-
pinnedTransparencyHead ? {
|
|
2542
|
-
knownTransparencyVersion: pinnedTransparencyHead.version,
|
|
2543
|
-
knownTransparencyHash: pinnedTransparencyHead.hash
|
|
2544
|
-
} : void 0
|
|
2545
|
-
);
|
|
2546
|
-
let witnessAnchorHead = null;
|
|
2547
|
-
if (!pinnedTransparencyHead) {
|
|
2548
|
-
const orgId = runtime.keyDirectory.directoryCheckpoint?.checkpoint.orgId ?? null;
|
|
2549
|
-
if (orgId && runtime.keyDirectory.directoryCheckpoint && runtime.keyDirectory.transparency) {
|
|
2550
|
-
witnessAnchorHead = await getPublicOrgWitnessHead(
|
|
2551
|
-
this.baseUrl,
|
|
2552
|
-
orgId,
|
|
2553
|
-
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
|
|
2554
991
|
);
|
|
2555
|
-
if (witnessAnchorHead) {
|
|
2556
|
-
if (runtime.keyDirectory.transparency.head.version < witnessAnchorHead.version) {
|
|
2557
|
-
throw new Error(
|
|
2558
|
-
`R4 SDK: public transparency witness head is ahead of the server response for org ${orgId}.`
|
|
2559
|
-
);
|
|
2560
|
-
}
|
|
2561
|
-
if (runtime.keyDirectory.transparency.head.version === witnessAnchorHead.version) {
|
|
2562
|
-
if (runtime.keyDirectory.transparency.head.hash !== witnessAnchorHead.hash) {
|
|
2563
|
-
throw new Error(`R4 SDK: public transparency witness head fork detected for org ${orgId}.`);
|
|
2564
|
-
}
|
|
2565
|
-
} else {
|
|
2566
|
-
runtime = await this.client.getTokenProviderRuntime(tokenProviderId, {
|
|
2567
|
-
knownTransparencyVersion: witnessAnchorHead.version,
|
|
2568
|
-
knownTransparencyHash: witnessAnchorHead.hash
|
|
2569
|
-
});
|
|
2570
|
-
}
|
|
2571
|
-
}
|
|
2572
992
|
}
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
runtime.vaultId,
|
|
2594
|
-
runtime.wrappedKey.encryptionKeyId,
|
|
2595
|
-
runtime.wrappedKey.signerEncryptionKeyId ?? runtime.wrappedKey.signerUserKeyPairId,
|
|
2596
|
-
runtime.wrappedKey.dekVersion,
|
|
2597
|
-
runtime.wrappedKey.wrappedDek,
|
|
2598
|
-
runtime.wrappedKey.wrappedDekSignature,
|
|
2599
|
-
wrappedKeySignerPublicKey
|
|
2600
|
-
)) {
|
|
2601
|
-
throw new Error(`R4 SDK: wrapped DEK signature verification failed for vault ${runtime.vaultId}.`);
|
|
2602
|
-
}
|
|
2603
|
-
const expectedDetailCheckpoint = buildVaultItemDetailCheckpointFromResponse(
|
|
2604
|
-
runtime.vaultItemDetail,
|
|
2605
|
-
runtime.vaultItemDetail.detailCheckpoint?.checkpoint.version ?? 0
|
|
2606
|
-
);
|
|
2607
|
-
if (!runtime.vaultItemDetail.detailCheckpoint) {
|
|
2608
|
-
throw new Error(`R4 SDK: vault item ${runtime.vaultItemDetail.id} is missing a signed detail checkpoint.`);
|
|
2609
|
-
}
|
|
2610
|
-
const detailVerified = verifyVaultItemDetailCheckpoint(
|
|
2611
|
-
expectedDetailCheckpoint,
|
|
2612
|
-
runtime.vaultItemDetail.detailCheckpoint.signature,
|
|
2613
|
-
getCheckpointSignerPublicKey({
|
|
2614
|
-
checkpoint: runtime.vaultItemDetail.detailCheckpoint,
|
|
2615
|
-
userKeys: trustedPublicKeys,
|
|
2616
|
-
agentKeys: trustedAgentPublicKeys
|
|
2617
|
-
})
|
|
2618
|
-
);
|
|
2619
|
-
if (!detailVerified) {
|
|
2620
|
-
throw new Error(
|
|
2621
|
-
`R4 SDK: vault item checkpoint verification failed for item ${runtime.vaultItemDetail.id}.`
|
|
2622
|
-
);
|
|
2623
|
-
}
|
|
2624
|
-
assertAndPinCheckpointVersion(
|
|
2625
|
-
this.trustStorePath,
|
|
2626
|
-
`detail:${runtime.vaultItemDetail.id}`,
|
|
2627
|
-
expectedDetailCheckpoint.version
|
|
2628
|
-
);
|
|
2629
|
-
return runtime;
|
|
2630
|
-
}
|
|
2631
|
-
decryptTokenProviderFields(runtime) {
|
|
2632
|
-
const dek = unwrapDEKWithPrivateKey(runtime.wrappedKey.wrappedDek, this.privateKeyPem);
|
|
2633
|
-
const fields = {};
|
|
2634
|
-
for (const field of runtime.vaultItemDetail.fields) {
|
|
2635
|
-
if (field.value === null) {
|
|
2636
|
-
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;
|
|
2637
1013
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
const historicalAgentSignerKey = agentKeys.flatMap((publicKey) => publicKey.transparency?.entries ?? []).find((entry) => entry.encryptionKeyId === wrappedKey.signerEncryptionKeyId);
|
|
2647
|
-
const wrappedKeySignerPublicKey = wrappedKey.signerEncryptionKeyId ? agentKeys.find(
|
|
2648
|
-
(publicKey) => publicKey.encryptionKeyId === wrappedKey.signerEncryptionKeyId
|
|
2649
|
-
)?.publicKey ?? historicalAgentSignerKey?.publicKey : signerKey?.publicKey;
|
|
2650
|
-
if (!wrappedKeySignerPublicKey) {
|
|
2651
|
-
throw new Error(
|
|
2652
|
-
`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
|
|
2653
1022
|
);
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
return 0;
|
|
2668
|
-
}
|
|
2669
|
-
toNonNegativeInt(value) {
|
|
2670
|
-
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
2671
|
-
return Math.floor(value);
|
|
2672
|
-
}
|
|
2673
|
-
return 0;
|
|
2674
|
-
}
|
|
2675
|
-
};
|
|
2676
|
-
var index_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";
|
|
2677
1036
|
|
|
2678
1037
|
// src/lib/doctor.ts
|
|
1038
|
+
import R4 from "@r4-sdk/node";
|
|
2679
1039
|
function getAgentIdFromRegistration(registration) {
|
|
2680
1040
|
const entryCount = registration?.transparency?.entries.length ?? 0;
|
|
2681
1041
|
if (!registration?.transparency || entryCount === 0) {
|
|
@@ -2949,7 +1309,7 @@ async function runDoctorChecks(connection) {
|
|
|
2949
1309
|
return report;
|
|
2950
1310
|
}
|
|
2951
1311
|
try {
|
|
2952
|
-
const r4 = await
|
|
1312
|
+
const r4 = await R4.create({
|
|
2953
1313
|
apiKey: connection.apiKey,
|
|
2954
1314
|
baseUrl: connection.baseUrl,
|
|
2955
1315
|
dev: connection.dev,
|
|
@@ -3065,8 +1425,8 @@ function doctorCommand(commandName = "doctor", description = "Verify CLI auth, r
|
|
|
3065
1425
|
}
|
|
3066
1426
|
|
|
3067
1427
|
// src/lib/credentials-file.ts
|
|
3068
|
-
import
|
|
3069
|
-
import
|
|
1428
|
+
import fs5 from "fs";
|
|
1429
|
+
import path3 from "path";
|
|
3070
1430
|
function normalizeFieldName(fieldName) {
|
|
3071
1431
|
return fieldName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3072
1432
|
}
|
|
@@ -3272,9 +1632,9 @@ function parsePlainTextContent(content) {
|
|
|
3272
1632
|
return {};
|
|
3273
1633
|
}
|
|
3274
1634
|
function parseCredentialsFile(credentialsFilePath) {
|
|
3275
|
-
const resolvedPath =
|
|
3276
|
-
const content =
|
|
3277
|
-
const extension =
|
|
1635
|
+
const resolvedPath = path3.resolve(credentialsFilePath);
|
|
1636
|
+
const content = fs5.readFileSync(resolvedPath, "utf8");
|
|
1637
|
+
const extension = path3.extname(resolvedPath).toLowerCase();
|
|
3278
1638
|
if (extension === ".json") {
|
|
3279
1639
|
return parseJsonContent(content);
|
|
3280
1640
|
}
|
|
@@ -3877,14 +2237,14 @@ function logoutCommand() {
|
|
|
3877
2237
|
import { Command as Command11 } from "commander";
|
|
3878
2238
|
|
|
3879
2239
|
// src/lib/public-auth-client.ts
|
|
3880
|
-
import
|
|
2240
|
+
import crypto2 from "crypto";
|
|
3881
2241
|
var PublicAuthClient = class {
|
|
3882
2242
|
baseUrl;
|
|
3883
2243
|
constructor(baseUrl) {
|
|
3884
2244
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
3885
2245
|
}
|
|
3886
|
-
async request(
|
|
3887
|
-
const response = await fetch(`${this.baseUrl}${
|
|
2246
|
+
async request(path6, body) {
|
|
2247
|
+
const response = await fetch(`${this.baseUrl}${path6}`, {
|
|
3888
2248
|
method: "POST",
|
|
3889
2249
|
headers: {
|
|
3890
2250
|
"Content-Type": "application/json",
|
|
@@ -3910,7 +2270,7 @@ var PublicAuthClient = class {
|
|
|
3910
2270
|
}
|
|
3911
2271
|
};
|
|
3912
2272
|
function solveAgentChallenge(nonce) {
|
|
3913
|
-
return
|
|
2273
|
+
return crypto2.createHash("sha256").update(nonce).digest("hex");
|
|
3914
2274
|
}
|
|
3915
2275
|
|
|
3916
2276
|
// src/lib/register-agent.ts
|
|
@@ -4290,8 +2650,8 @@ import { Command as Command16 } from "commander";
|
|
|
4290
2650
|
import ora10 from "ora";
|
|
4291
2651
|
|
|
4292
2652
|
// src/lib/json-input.ts
|
|
4293
|
-
import
|
|
4294
|
-
import
|
|
2653
|
+
import fs6 from "fs";
|
|
2654
|
+
import path4 from "path";
|
|
4295
2655
|
function parseJsonInput(params) {
|
|
4296
2656
|
const bodyFlagName = params.bodyFlagName ?? "--body";
|
|
4297
2657
|
const bodyFileFlagName = params.bodyFileFlagName ?? "--body-file";
|
|
@@ -4301,7 +2661,7 @@ function parseJsonInput(params) {
|
|
|
4301
2661
|
if (!params.body && !params.bodyFile) {
|
|
4302
2662
|
return void 0;
|
|
4303
2663
|
}
|
|
4304
|
-
const rawInput = params.bodyFile ?
|
|
2664
|
+
const rawInput = params.bodyFile ? fs6.readFileSync(path4.resolve(params.bodyFile), "utf8") : params.body;
|
|
4305
2665
|
const sourceLabel = params.bodyFile ? `${bodyFileFlagName} ${params.bodyFile}` : bodyFlagName;
|
|
4306
2666
|
try {
|
|
4307
2667
|
return JSON.parse(rawInput);
|
|
@@ -4402,7 +2762,7 @@ function registerBudgetCommands(program2) {
|
|
|
4402
2762
|
|
|
4403
2763
|
// src/commands/configure.ts
|
|
4404
2764
|
import { Command as Command18 } from "commander";
|
|
4405
|
-
import
|
|
2765
|
+
import fs7 from "fs";
|
|
4406
2766
|
async function promptRuntimeTarget(existingProfile) {
|
|
4407
2767
|
const defaultTarget = existingProfile.baseUrl ? "custom" : existingProfile.dev ? "dev" : "prod";
|
|
4408
2768
|
const runtimeTarget = await promptChoice(
|
|
@@ -4435,7 +2795,7 @@ async function promptRuntimeTarget(existingProfile) {
|
|
|
4435
2795
|
}
|
|
4436
2796
|
async function resolvePrivateKeyMaterial(profileName, existingProfile) {
|
|
4437
2797
|
const managedPrivateKeyPath = getDefaultPrivateKeyPath(profileName);
|
|
4438
|
-
const defaultMode = existingProfile.privateKeyPath ||
|
|
2798
|
+
const defaultMode = existingProfile.privateKeyPath || fs7.existsSync(managedPrivateKeyPath) ? "existing" : "generate";
|
|
4439
2799
|
const privateKeyMode = await promptChoice(
|
|
4440
2800
|
"How should this profile handle its local private key?",
|
|
4441
2801
|
[
|
|
@@ -4738,13 +3098,13 @@ function normalizeMethod(method) {
|
|
|
4738
3098
|
function requestCommand() {
|
|
4739
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(
|
|
4740
3100
|
withErrorHandler(
|
|
4741
|
-
async (method,
|
|
3101
|
+
async (method, path6, options, cmd) => {
|
|
4742
3102
|
const globalOpts = cmd.optsWithGlobals();
|
|
4743
3103
|
const config = resolveAuth(globalOpts);
|
|
4744
3104
|
const client = new CliClient(config.apiKey, config.baseUrl);
|
|
4745
3105
|
const result = await client.requestMachine({
|
|
4746
3106
|
method: normalizeMethod(method),
|
|
4747
|
-
path:
|
|
3107
|
+
path: path6,
|
|
4748
3108
|
body: parseJsonInput({
|
|
4749
3109
|
body: options.body,
|
|
4750
3110
|
bodyFile: options.bodyFile
|
|
@@ -5049,17 +3409,64 @@ function createItemCommand() {
|
|
|
5049
3409
|
);
|
|
5050
3410
|
}
|
|
5051
3411
|
|
|
5052
|
-
// src/commands/vault/
|
|
3412
|
+
// src/commands/vault/download-asset.ts
|
|
3413
|
+
import path5 from "path";
|
|
5053
3414
|
import { Command as Command32 } from "commander";
|
|
5054
3415
|
import ora21 from "ora";
|
|
5055
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";
|
|
5056
3463
|
function listCommand5() {
|
|
5057
|
-
return new
|
|
3464
|
+
return new Command33("list").description("List all locally decrypted environment variables").action(
|
|
5058
3465
|
withErrorHandler(async (_opts, cmd) => {
|
|
5059
3466
|
const globalOpts = cmd.optsWithGlobals();
|
|
5060
3467
|
const config = resolveAuth(globalOpts);
|
|
5061
|
-
const spinner =
|
|
5062
|
-
const r4 = await
|
|
3468
|
+
const spinner = ora22("Fetching environment variables...").start();
|
|
3469
|
+
const r4 = await R43.create(config);
|
|
5063
3470
|
spinner.stop();
|
|
5064
3471
|
const env = r4.env;
|
|
5065
3472
|
const keys = Object.keys(env).sort();
|
|
@@ -5080,12 +3487,13 @@ function listCommand5() {
|
|
|
5080
3487
|
}
|
|
5081
3488
|
|
|
5082
3489
|
// src/commands/vault/list-items.ts
|
|
5083
|
-
import { Command as
|
|
3490
|
+
import { Command as Command35 } from "commander";
|
|
5084
3491
|
|
|
5085
3492
|
// src/commands/vault/items.ts
|
|
5086
|
-
import { Command as
|
|
5087
|
-
import
|
|
5088
|
-
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]";
|
|
5089
3497
|
function deriveItems(env) {
|
|
5090
3498
|
const keys = Object.keys(env).sort();
|
|
5091
3499
|
return keys.map((key) => ({
|
|
@@ -5096,15 +3504,18 @@ function deriveItems(env) {
|
|
|
5096
3504
|
async function loadVaultMetadataRows(globalOpts) {
|
|
5097
3505
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5098
3506
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5099
|
-
const { vaults } = await
|
|
5100
|
-
|
|
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();
|
|
5101
3512
|
for (const vault of vaults) {
|
|
5102
3513
|
const response = await client.listVaultItems(vault.id);
|
|
5103
3514
|
const groupNames = Object.fromEntries(
|
|
5104
3515
|
response.vaultItemGroups.map((group) => [group.id, group.name])
|
|
5105
3516
|
);
|
|
5106
3517
|
for (const item of response.items) {
|
|
5107
|
-
|
|
3518
|
+
rowsByItemId.set(item.id, {
|
|
5108
3519
|
vaultId: vault.id,
|
|
5109
3520
|
vaultName: vault.name,
|
|
5110
3521
|
itemId: item.id,
|
|
@@ -5116,13 +3527,33 @@ async function loadVaultMetadataRows(globalOpts) {
|
|
|
5116
3527
|
});
|
|
5117
3528
|
}
|
|
5118
3529
|
}
|
|
5119
|
-
|
|
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) => {
|
|
5120
3551
|
const vaultCompare = left.vaultName.localeCompare(right.vaultName);
|
|
5121
3552
|
return vaultCompare !== 0 ? vaultCompare : left.itemName.localeCompare(right.itemName);
|
|
5122
3553
|
});
|
|
5123
3554
|
}
|
|
5124
3555
|
async function renderVaultMetadataRows(globalOpts) {
|
|
5125
|
-
const spinner =
|
|
3556
|
+
const spinner = ora23("Fetching vault item metadata...").start();
|
|
5126
3557
|
const rows = await loadVaultMetadataRows(globalOpts);
|
|
5127
3558
|
spinner.stop();
|
|
5128
3559
|
if (globalOpts.json) {
|
|
@@ -5140,7 +3571,7 @@ async function renderVaultMetadataRows(globalOpts) {
|
|
|
5140
3571
|
row.vaultName,
|
|
5141
3572
|
row.itemName,
|
|
5142
3573
|
row.type || "-",
|
|
5143
|
-
String(row.fieldCount),
|
|
3574
|
+
row.fieldCount === null ? "-" : String(row.fieldCount),
|
|
5144
3575
|
row.groupName || "-",
|
|
5145
3576
|
row.websites.join(", ") || "-"
|
|
5146
3577
|
]),
|
|
@@ -5149,7 +3580,7 @@ async function renderVaultMetadataRows(globalOpts) {
|
|
|
5149
3580
|
console.log();
|
|
5150
3581
|
}
|
|
5151
3582
|
function itemsCommand() {
|
|
5152
|
-
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(
|
|
5153
3584
|
withErrorHandler(async (opts, cmd) => {
|
|
5154
3585
|
const globalOpts = cmd.optsWithGlobals();
|
|
5155
3586
|
if (opts.metadataOnly) {
|
|
@@ -5157,8 +3588,8 @@ function itemsCommand() {
|
|
|
5157
3588
|
return;
|
|
5158
3589
|
}
|
|
5159
3590
|
const config = resolveAuth(globalOpts);
|
|
5160
|
-
const spinner =
|
|
5161
|
-
const r4 = await
|
|
3591
|
+
const spinner = ora23("Fetching vault items...").start();
|
|
3592
|
+
const r4 = await R44.create(config);
|
|
5162
3593
|
spinner.stop();
|
|
5163
3594
|
const env = r4.env;
|
|
5164
3595
|
const items = deriveItems(env);
|
|
@@ -5184,7 +3615,7 @@ function itemsCommand() {
|
|
|
5184
3615
|
|
|
5185
3616
|
// src/commands/vault/list-items.ts
|
|
5186
3617
|
function listItemsCommand() {
|
|
5187
|
-
return new
|
|
3618
|
+
return new Command35("list-items").description("List vault item metadata only, without requiring local decryption").action(
|
|
5188
3619
|
withErrorHandler(async (_opts, cmd) => {
|
|
5189
3620
|
const globalOpts = cmd.optsWithGlobals();
|
|
5190
3621
|
await renderVaultMetadataRows(globalOpts);
|
|
@@ -5193,15 +3624,15 @@ function listItemsCommand() {
|
|
|
5193
3624
|
}
|
|
5194
3625
|
|
|
5195
3626
|
// src/commands/vault/list-vaults.ts
|
|
5196
|
-
import { Command as
|
|
5197
|
-
import
|
|
3627
|
+
import { Command as Command36 } from "commander";
|
|
3628
|
+
import ora24 from "ora";
|
|
5198
3629
|
function listVaultsCommand() {
|
|
5199
|
-
return new
|
|
3630
|
+
return new Command36("list-vaults").description("List visible vaults without requiring local decryption").action(
|
|
5200
3631
|
withErrorHandler(async (_opts, cmd) => {
|
|
5201
3632
|
const globalOpts = cmd.optsWithGlobals();
|
|
5202
3633
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5203
3634
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5204
|
-
const spinner =
|
|
3635
|
+
const spinner = ora24("Fetching visible vaults...").start();
|
|
5205
3636
|
const response = await client.listVaults(connection.projectId);
|
|
5206
3637
|
spinner.stop();
|
|
5207
3638
|
if (globalOpts.json) {
|
|
@@ -5230,17 +3661,17 @@ function listVaultsCommand() {
|
|
|
5230
3661
|
}
|
|
5231
3662
|
|
|
5232
3663
|
// src/commands/vault/get.ts
|
|
5233
|
-
import { Command as
|
|
5234
|
-
import
|
|
5235
|
-
import
|
|
3664
|
+
import { Command as Command37 } from "commander";
|
|
3665
|
+
import ora25 from "ora";
|
|
3666
|
+
import R45 from "@r4-sdk/node";
|
|
5236
3667
|
function getCommand2() {
|
|
5237
|
-
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(
|
|
5238
3669
|
withErrorHandler(
|
|
5239
3670
|
async (keyArg, _opts, cmd) => {
|
|
5240
3671
|
const globalOpts = cmd.optsWithGlobals();
|
|
5241
3672
|
const config = resolveAuth(globalOpts);
|
|
5242
|
-
const spinner =
|
|
5243
|
-
const r4 = await
|
|
3673
|
+
const spinner = ora25("Fetching environment variables...").start();
|
|
3674
|
+
const r4 = await R45.create(config);
|
|
5244
3675
|
spinner.stop();
|
|
5245
3676
|
const env = r4.env;
|
|
5246
3677
|
const key = keyArg.toUpperCase();
|
|
@@ -5260,17 +3691,17 @@ function getCommand2() {
|
|
|
5260
3691
|
}
|
|
5261
3692
|
|
|
5262
3693
|
// src/commands/vault/search.ts
|
|
5263
|
-
import { Command as
|
|
5264
|
-
import
|
|
5265
|
-
import
|
|
3694
|
+
import { Command as Command38 } from "commander";
|
|
3695
|
+
import ora26 from "ora";
|
|
3696
|
+
import R46 from "@r4-sdk/node";
|
|
5266
3697
|
function searchCommand() {
|
|
5267
|
-
return new
|
|
3698
|
+
return new Command38("search").description("Search vault items by name").argument("<query>", "Search query (case-insensitive match against key names)").action(
|
|
5268
3699
|
withErrorHandler(
|
|
5269
3700
|
async (query, _opts, cmd) => {
|
|
5270
3701
|
const globalOpts = cmd.optsWithGlobals();
|
|
5271
3702
|
const config = resolveAuth(globalOpts);
|
|
5272
|
-
const spinner =
|
|
5273
|
-
const r4 = await
|
|
3703
|
+
const spinner = ora26("Searching vault items...").start();
|
|
3704
|
+
const r4 = await R46.create(config);
|
|
5274
3705
|
spinner.stop();
|
|
5275
3706
|
const env = r4.env;
|
|
5276
3707
|
const lowerQuery = query.toLowerCase();
|
|
@@ -5300,6 +3731,7 @@ function registerVaultCommands(program2) {
|
|
|
5300
3731
|
const vault = program2.command("vault").description("Manage vault secrets");
|
|
5301
3732
|
vault.addCommand(createCommand3());
|
|
5302
3733
|
vault.addCommand(createItemCommand());
|
|
3734
|
+
vault.addCommand(downloadAssetCommand());
|
|
5303
3735
|
vault.addCommand(listCommand5());
|
|
5304
3736
|
vault.addCommand(listVaultsCommand());
|
|
5305
3737
|
vault.addCommand(listItemsCommand());
|
|
@@ -5309,16 +3741,16 @@ function registerVaultCommands(program2) {
|
|
|
5309
3741
|
}
|
|
5310
3742
|
|
|
5311
3743
|
// src/commands/project/add-vault.ts
|
|
5312
|
-
import { Command as
|
|
5313
|
-
import
|
|
3744
|
+
import { Command as Command39 } from "commander";
|
|
3745
|
+
import ora27 from "ora";
|
|
5314
3746
|
function addVaultCommand() {
|
|
5315
|
-
return new
|
|
3747
|
+
return new Command39("add-vault").description("Associate a vault with a project").argument("<projectId>", "Project ID").argument("<vaultId>", "Vault ID").action(
|
|
5316
3748
|
withErrorHandler(
|
|
5317
3749
|
async (projectId, vaultId, _opts, cmd) => {
|
|
5318
3750
|
const globalOpts = cmd.optsWithGlobals();
|
|
5319
3751
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5320
3752
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5321
|
-
const spinner =
|
|
3753
|
+
const spinner = ora27("Associating vault with project...").start();
|
|
5322
3754
|
await client.associateProjectVault({
|
|
5323
3755
|
projectId,
|
|
5324
3756
|
vaultId
|
|
@@ -5335,15 +3767,15 @@ function addVaultCommand() {
|
|
|
5335
3767
|
}
|
|
5336
3768
|
|
|
5337
3769
|
// src/commands/project/list.ts
|
|
5338
|
-
import { Command as
|
|
5339
|
-
import
|
|
3770
|
+
import { Command as Command40 } from "commander";
|
|
3771
|
+
import ora28 from "ora";
|
|
5340
3772
|
function listCommand6() {
|
|
5341
|
-
return new
|
|
3773
|
+
return new Command40("list").description("List all projects").action(
|
|
5342
3774
|
withErrorHandler(async (_opts, cmd) => {
|
|
5343
3775
|
const globalOpts = cmd.optsWithGlobals();
|
|
5344
3776
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5345
3777
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5346
|
-
const spinner =
|
|
3778
|
+
const spinner = ora28("Fetching projects...").start();
|
|
5347
3779
|
const response = await client.listProjects();
|
|
5348
3780
|
spinner.stop();
|
|
5349
3781
|
const rows = response.projects.map((p) => [
|
|
@@ -5367,16 +3799,16 @@ function listCommand6() {
|
|
|
5367
3799
|
}
|
|
5368
3800
|
|
|
5369
3801
|
// src/commands/project/get.ts
|
|
5370
|
-
import { Command as
|
|
3802
|
+
import { Command as Command41 } from "commander";
|
|
5371
3803
|
import chalk6 from "chalk";
|
|
5372
|
-
import
|
|
3804
|
+
import ora29 from "ora";
|
|
5373
3805
|
function getCommand3() {
|
|
5374
|
-
return new
|
|
3806
|
+
return new Command41("get").description("Get project details").argument("<id>", "Project ID").action(
|
|
5375
3807
|
withErrorHandler(async (id, _opts, cmd) => {
|
|
5376
3808
|
const globalOpts = cmd.optsWithGlobals();
|
|
5377
3809
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5378
3810
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5379
|
-
const spinner =
|
|
3811
|
+
const spinner = ora29("Fetching project...").start();
|
|
5380
3812
|
const project = await client.getProject(id);
|
|
5381
3813
|
spinner.stop();
|
|
5382
3814
|
if (globalOpts.json) {
|
|
@@ -5434,9 +3866,9 @@ function getCommand3() {
|
|
|
5434
3866
|
}
|
|
5435
3867
|
|
|
5436
3868
|
// src/commands/project/create.ts
|
|
5437
|
-
import { Command as
|
|
3869
|
+
import { Command as Command42 } from "commander";
|
|
5438
3870
|
import readline2 from "readline";
|
|
5439
|
-
import
|
|
3871
|
+
import ora30 from "ora";
|
|
5440
3872
|
function prompt(question) {
|
|
5441
3873
|
const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
|
|
5442
3874
|
return new Promise((resolve) => {
|
|
@@ -5447,7 +3879,7 @@ function prompt(question) {
|
|
|
5447
3879
|
});
|
|
5448
3880
|
}
|
|
5449
3881
|
function createCommand4() {
|
|
5450
|
-
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(
|
|
5451
3883
|
withErrorHandler(async (opts, cmd) => {
|
|
5452
3884
|
const globalOpts = cmd.optsWithGlobals();
|
|
5453
3885
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
@@ -5459,7 +3891,7 @@ function createCommand4() {
|
|
|
5459
3891
|
if (!name) {
|
|
5460
3892
|
throw new Error("Project name is required.");
|
|
5461
3893
|
}
|
|
5462
|
-
const spinner =
|
|
3894
|
+
const spinner = ora30("Creating project...").start();
|
|
5463
3895
|
const response = await client.createProject({
|
|
5464
3896
|
name,
|
|
5465
3897
|
description: opts.description,
|
|
@@ -5476,10 +3908,10 @@ function createCommand4() {
|
|
|
5476
3908
|
}
|
|
5477
3909
|
|
|
5478
3910
|
// src/commands/project/set-agents.ts
|
|
5479
|
-
import { Command as
|
|
5480
|
-
import
|
|
3911
|
+
import { Command as Command43 } from "commander";
|
|
3912
|
+
import ora31 from "ora";
|
|
5481
3913
|
function setAgentsCommand() {
|
|
5482
|
-
return new
|
|
3914
|
+
return new Command43("set-agents").description("Replace the explicit agent membership for a project").argument("<projectId>", "Project ID").option(
|
|
5483
3915
|
"--agent-id <id>",
|
|
5484
3916
|
"Agent ID to keep on the project (repeat or use comma-separated values)",
|
|
5485
3917
|
collectOptionValues,
|
|
@@ -5490,7 +3922,7 @@ function setAgentsCommand() {
|
|
|
5490
3922
|
const globalOpts = cmd.optsWithGlobals();
|
|
5491
3923
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5492
3924
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5493
|
-
const spinner =
|
|
3925
|
+
const spinner = ora31("Updating project agents...").start();
|
|
5494
3926
|
await client.updateProjectAgents(projectId, {
|
|
5495
3927
|
agentIds: options.agentId
|
|
5496
3928
|
});
|
|
@@ -5517,16 +3949,16 @@ function registerProjectCommands(program2) {
|
|
|
5517
3949
|
|
|
5518
3950
|
// src/commands/run/index.ts
|
|
5519
3951
|
import { spawn } from "child_process";
|
|
5520
|
-
import
|
|
5521
|
-
import
|
|
3952
|
+
import ora32 from "ora";
|
|
3953
|
+
import R47 from "@r4-sdk/node";
|
|
5522
3954
|
function registerRunCommand(program2) {
|
|
5523
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(
|
|
5524
3956
|
withErrorHandler(
|
|
5525
3957
|
async (commandParts, opts, cmd) => {
|
|
5526
3958
|
const globalOpts = cmd.optsWithGlobals();
|
|
5527
3959
|
const config = resolveAuth(globalOpts);
|
|
5528
|
-
const spinner =
|
|
5529
|
-
const r4 = await
|
|
3960
|
+
const spinner = ora32("Loading vault secrets...").start();
|
|
3961
|
+
const r4 = await R47.create(config);
|
|
5530
3962
|
spinner.stop();
|
|
5531
3963
|
const env = r4.env;
|
|
5532
3964
|
const secretEnv = {};
|
|
@@ -5552,10 +3984,10 @@ function registerRunCommand(program2) {
|
|
|
5552
3984
|
}
|
|
5553
3985
|
|
|
5554
3986
|
// src/commands/security-group/create.ts
|
|
5555
|
-
import { Command as
|
|
5556
|
-
import
|
|
3987
|
+
import { Command as Command44 } from "commander";
|
|
3988
|
+
import ora33 from "ora";
|
|
5557
3989
|
function createCommand5() {
|
|
5558
|
-
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(
|
|
5559
3991
|
"--role <role>",
|
|
5560
3992
|
"Tenant role to grant through this group (repeat or use comma-separated values)",
|
|
5561
3993
|
collectOptionValues,
|
|
@@ -5566,7 +3998,7 @@ function createCommand5() {
|
|
|
5566
3998
|
const globalOpts = cmd.optsWithGlobals();
|
|
5567
3999
|
const connection = resolveConnection(globalOpts, { requireApiKey: true });
|
|
5568
4000
|
const client = new CliClient(connection.apiKey, connection.baseUrl);
|
|
5569
|
-
const spinner =
|
|
4001
|
+
const spinner = ora33("Creating security group...").start();
|
|
5570
4002
|
await client.createSecurityGroup({
|
|
5571
4003
|
name: options.name,
|
|
5572
4004
|
parentId: options.parentId ?? null,
|
|
@@ -5602,8 +4034,8 @@ function registerSecurityGroupCommands(program2) {
|
|
|
5602
4034
|
}
|
|
5603
4035
|
|
|
5604
4036
|
// src/index.ts
|
|
5605
|
-
var program = new
|
|
5606
|
-
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);
|
|
5607
4039
|
registerAgentCommands(program);
|
|
5608
4040
|
registerAuthCommands(program);
|
|
5609
4041
|
registerBillingCommands(program);
|