@r4security/sdk 0.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/lib/index.cjs ADDED
@@ -0,0 +1,1093 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ R4: () => R4,
34
+ default: () => src_default
35
+ });
36
+ module.exports = __toCommonJS(src_exports);
37
+ var import_node_path3 = __toESM(require("path"), 1);
38
+
39
+ // src/client.ts
40
+ var R4Client = class {
41
+ apiKey;
42
+ baseUrl;
43
+ constructor(apiKey, baseUrl) {
44
+ this.apiKey = apiKey;
45
+ this.baseUrl = baseUrl.replace(/\/$/, "");
46
+ }
47
+ buildHeaders() {
48
+ return {
49
+ "X-API-Key": this.apiKey,
50
+ "Content-Type": "application/json"
51
+ };
52
+ }
53
+ async request(path4, init) {
54
+ const response = await fetch(`${this.baseUrl}${path4}`, {
55
+ ...init,
56
+ headers: {
57
+ ...this.buildHeaders(),
58
+ ...init.headers ?? {}
59
+ }
60
+ });
61
+ if (!response.ok) {
62
+ const errorBody = await response.json().catch(() => ({}));
63
+ const errorMessage = typeof errorBody.error?.message === "string" ? errorBody.error.message : `HTTP ${response.status}: ${response.statusText}`;
64
+ throw new Error(`R4 API Error: ${errorMessage}`);
65
+ }
66
+ if (response.status === 204) {
67
+ return void 0;
68
+ }
69
+ return response.json();
70
+ }
71
+ /**
72
+ * Registers or re-confirms the agent runtime's local RSA public key.
73
+ */
74
+ async registerAgentPublicKey(body) {
75
+ return this.request("/api/v1/machine/vault/public-key", {
76
+ method: "POST",
77
+ body: JSON.stringify(body)
78
+ });
79
+ }
80
+ /**
81
+ * Lists all accessible non-hidden vaults. When `projectId` is provided, the
82
+ * backend additionally filters to vaults associated with that project.
83
+ */
84
+ async listVaults(projectId) {
85
+ const search = projectId ? `?projectId=${encodeURIComponent(projectId)}` : "";
86
+ return this.request(`/api/v1/machine/vault${search}`, {
87
+ method: "GET"
88
+ });
89
+ }
90
+ /**
91
+ * Retrieves the active wrapped DEK for the authenticated agent on a vault.
92
+ */
93
+ async getAgentWrappedKey(vaultId) {
94
+ return this.request(
95
+ `/api/v1/machine/vault/${encodeURIComponent(vaultId)}/wrapped-key`,
96
+ { method: "GET" }
97
+ );
98
+ }
99
+ /**
100
+ * Retrieves the trusted user-key directory for a vault so the runtime can
101
+ * verify wrapped-DEK signatures locally.
102
+ */
103
+ async getVaultUserKeyDirectory(vaultId, params) {
104
+ const searchParams = new URLSearchParams();
105
+ if (params?.knownTransparencyVersion !== void 0) {
106
+ searchParams.set("knownTransparencyVersion", String(params.knownTransparencyVersion));
107
+ }
108
+ if (params?.knownTransparencyHash) {
109
+ searchParams.set("knownTransparencyHash", params.knownTransparencyHash);
110
+ }
111
+ const search = searchParams.size > 0 ? `?${searchParams.toString()}` : "";
112
+ return this.request(
113
+ `/api/v1/machine/vault/${encodeURIComponent(vaultId)}/public-keys${search}`,
114
+ { method: "GET" }
115
+ );
116
+ }
117
+ /**
118
+ * Lists all items in a vault with lightweight metadata.
119
+ */
120
+ async listVaultItems(vaultId) {
121
+ return this.request(
122
+ `/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items`,
123
+ { method: "GET" }
124
+ );
125
+ }
126
+ /**
127
+ * Retrieves the full field payloads for a vault item.
128
+ */
129
+ async getVaultItemDetail(vaultId, itemId) {
130
+ return this.request(
131
+ `/api/v1/machine/vault/${encodeURIComponent(vaultId)}/items/${encodeURIComponent(itemId)}`,
132
+ { method: "GET" }
133
+ );
134
+ }
135
+ };
136
+
137
+ // src/crypto.ts
138
+ var import_node_crypto = __toESM(require("crypto"), 1);
139
+ var import_node_fs = __toESM(require("fs"), 1);
140
+ var import_node_path = __toESM(require("path"), 1);
141
+
142
+ // src/transparency.ts
143
+ var TRANSPARENCY_WITNESS_PAYLOAD_PREFIX = "r4-transparency-witness-v1";
144
+ var DEFAULT_TRANSPARENCY_WITNESS_URL = "https://transparency.r4.dev";
145
+ var TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
146
+ MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JhILFiS/BOWR9laubW
147
+ g2vepQy26BXAlnrscZZVQUzBBaCM4hWobpt3Nh77vxP0gqVAJXP1hVhPPwxGQnOF
148
+ 4Qg/RK4iEETjMdmh3KMqFX9MeE9tP4cTOGtsgWsedNpu6TvMT+2vu+0ltmr7p4Xv
149
+ H0ID48Q8JLeNksc/RekrsfzQ9DVtXFS7z1FF2VQgzamdJsW9hGMiM7Q+0iXei7PW
150
+ 3PsLd1aNtqJ3lIj3t12qFiJiYyKF0hEq0//Abgb9SgDv/WOlRG1Ianf1/fnP2jer
151
+ ZYiZSylXqQdun0Db2d0+FDm/znV2AGAmBEXm6qnCogEHu77LoLyCyJOlB9WNtRwh
152
+ KnbzTmE2Mw/43jxvCcR7pE5kik/tdeMvqGFZfg3ozUG9eM0q0TURH6g9b9J4sBnR
153
+ dxz2PbF4cl/AeL4ANPmLz3kUQaDA6wR0veVk5jV+Uqr55TYz/zEbY1rtJbmnc53Q
154
+ ihPS6xtSiexrqnOgqm/AVbiRhxjPqfg3/VJM3zR5Blnu02AqVR9kCT0WkyEWRz5X
155
+ 6HU8DEocJIPz8UwBMKQ7rnjMPv/Fjpuav/EIad5vOdfxCZkjyTYoQg8vLUyfXvgD
156
+ mBWFgKIN8GTRyM+LjZIgznjN58dZ8ZvsGd14oKnH7WgAh9FVh8ri7gNmsdJeRTn/
157
+ 2zDkTlx+FQxAxqFaYV7qCvcCAwEAAQ==
158
+ -----END PUBLIC KEY-----`;
159
+ var buildOrgUserKeyDirectoryWitnessPayload = (orgId, head) => [
160
+ TRANSPARENCY_WITNESS_PAYLOAD_PREFIX,
161
+ "org-user-key-directory",
162
+ orgId,
163
+ String(head.version),
164
+ head.hash
165
+ ].join(":");
166
+ var buildOrgUserKeyDirectoryWitnessPath = (orgId) => `v1/orgs/${orgId}/user-key-directory-head.json`;
167
+ var buildTransparencyWitnessUrl = (baseUrl, path4) => `${baseUrl.replace(/\/+$/, "")}/${path4.replace(/^\/+/, "")}`;
168
+ var shouldUseDefaultTransparencyWitness = (apiBaseUrl) => /(^https?:\/\/)?([^.]+\.)?r4\.dev(?::\d+)?(\/|$)/i.test(apiBaseUrl.trim());
169
+
170
+ // src/crypto.ts
171
+ var RSA_OAEP_CONFIG = {
172
+ padding: import_node_crypto.default.constants.RSA_PKCS1_OAEP_PADDING,
173
+ oaepHash: "sha256"
174
+ };
175
+ var RSA_PSS_SIGN_CONFIG = {
176
+ padding: import_node_crypto.default.constants.RSA_PKCS1_PSS_PADDING,
177
+ saltLength: 32
178
+ };
179
+ var USER_KEY_ROTATION_PREFIX = "r4-user-key-rotation-v1";
180
+ var USER_KEY_DIRECTORY_CHECKPOINT_PREFIX = "r4-user-key-directory-checkpoint-v1";
181
+ var USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX = "r4-user-key-directory-transparency-entry-v1";
182
+ var WRAPPED_DEK_SIGNATURE_PREFIX = "r4-wrapped-dek-signature-v1";
183
+ var VAULT_SUMMARY_CHECKPOINT_PREFIX = "r4-vault-summary-checkpoint-v1";
184
+ var VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX = "r4-vault-item-detail-checkpoint-v1";
185
+ function pemToDer(pem, beginLabel, endLabel) {
186
+ const derBase64 = pem.replace(beginLabel, "").replace(endLabel, "").replace(/\s/g, "");
187
+ return Buffer.from(derBase64, "base64");
188
+ }
189
+ function getWrappedDekFingerprint(wrappedDek) {
190
+ return import_node_crypto.default.createHash("sha256").update(Buffer.from(wrappedDek, "base64")).digest("hex");
191
+ }
192
+ function getCheckpointFingerprint(prefix, canonicalJson) {
193
+ return `${prefix}:${import_node_crypto.default.createHash("sha256").update(canonicalJson, "utf8").digest("hex")}`;
194
+ }
195
+ function loadPrivateKey(privateKeyPath) {
196
+ return import_node_fs.default.readFileSync(import_node_path.default.resolve(privateKeyPath), "utf8").trim();
197
+ }
198
+ function derivePublicKey(privateKeyPem) {
199
+ return import_node_crypto.default.createPublicKey(privateKeyPem).export({
200
+ type: "spki",
201
+ format: "pem"
202
+ }).toString();
203
+ }
204
+ function getPublicKeyFingerprint(publicKeyPem) {
205
+ const derBytes = pemToDer(publicKeyPem, "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----");
206
+ return import_node_crypto.default.createHash("sha256").update(derBytes).digest("hex");
207
+ }
208
+ function buildUserKeyRotationPayload(previousUserKeyPairId, newPublicKeyFingerprint) {
209
+ return `${USER_KEY_ROTATION_PREFIX}:${previousUserKeyPairId}:${newPublicKeyFingerprint}`;
210
+ }
211
+ function verifyUserKeyRotation(previousUserKeyPairId, newPublicKeyPem, rotationSignature, previousPublicKeyPem) {
212
+ const payload = buildUserKeyRotationPayload(
213
+ previousUserKeyPairId,
214
+ getPublicKeyFingerprint(newPublicKeyPem)
215
+ );
216
+ try {
217
+ return import_node_crypto.default.verify(
218
+ "sha256",
219
+ Buffer.from(payload, "utf8"),
220
+ {
221
+ key: previousPublicKeyPem,
222
+ ...RSA_PSS_SIGN_CONFIG
223
+ },
224
+ Buffer.from(rotationSignature, "base64")
225
+ );
226
+ } catch {
227
+ return false;
228
+ }
229
+ }
230
+ function verifyTransparencyWitnessPayload(payload, signature, publicKeyPem) {
231
+ try {
232
+ return import_node_crypto.default.verify(
233
+ "sha256",
234
+ Buffer.from(payload, "utf8"),
235
+ {
236
+ key: publicKeyPem,
237
+ ...RSA_PSS_SIGN_CONFIG
238
+ },
239
+ Buffer.from(signature, "base64")
240
+ );
241
+ } catch {
242
+ return false;
243
+ }
244
+ }
245
+ function verifyOrgUserKeyDirectoryWitnessArtifact(artifact, publicKeyPem) {
246
+ return verifyTransparencyWitnessPayload(
247
+ buildOrgUserKeyDirectoryWitnessPayload(artifact.orgId, artifact.head),
248
+ artifact.signature,
249
+ publicKeyPem
250
+ );
251
+ }
252
+ function normalizeUserKeyDirectoryCheckpoint(checkpoint) {
253
+ return {
254
+ orgId: checkpoint.orgId,
255
+ version: checkpoint.version,
256
+ entries: [...checkpoint.entries].map((entry) => ({
257
+ userKeyPairId: entry.userKeyPairId,
258
+ orgUserId: entry.orgUserId,
259
+ fingerprint: entry.fingerprint,
260
+ previousUserKeyPairId: entry.previousUserKeyPairId ?? null,
261
+ rotationSignature: entry.rotationSignature ?? null
262
+ })).sort((left, right) => left.userKeyPairId.localeCompare(right.userKeyPairId))
263
+ };
264
+ }
265
+ function canonicalizeUserKeyDirectoryCheckpoint(checkpoint) {
266
+ return JSON.stringify(normalizeUserKeyDirectoryCheckpoint(checkpoint));
267
+ }
268
+ function buildUserKeyDirectoryCheckpointPayload(checkpoint) {
269
+ return getCheckpointFingerprint(
270
+ USER_KEY_DIRECTORY_CHECKPOINT_PREFIX,
271
+ canonicalizeUserKeyDirectoryCheckpoint(checkpoint)
272
+ );
273
+ }
274
+ function verifyUserKeyDirectoryCheckpoint(checkpoint, signature, publicKeyPem) {
275
+ try {
276
+ return import_node_crypto.default.verify(
277
+ "sha256",
278
+ Buffer.from(buildUserKeyDirectoryCheckpointPayload(checkpoint), "utf8"),
279
+ {
280
+ key: publicKeyPem,
281
+ ...RSA_PSS_SIGN_CONFIG
282
+ },
283
+ Buffer.from(signature, "base64")
284
+ );
285
+ } catch {
286
+ return false;
287
+ }
288
+ }
289
+ function buildUserKeyDirectoryTransparencyEntryHash(entry) {
290
+ return getCheckpointFingerprint(
291
+ USER_KEY_DIRECTORY_TRANSPARENCY_ENTRY_PREFIX,
292
+ JSON.stringify({
293
+ orgId: entry.orgId,
294
+ version: entry.version,
295
+ directoryCheckpointPayload: entry.directoryCheckpointPayload,
296
+ signerUserKeyPairId: entry.signerUserKeyPairId,
297
+ signerOrgUserId: entry.signerOrgUserId,
298
+ signerFingerprint: entry.signerFingerprint,
299
+ signature: entry.signature,
300
+ previousEntryHash: entry.previousEntryHash ?? null
301
+ })
302
+ );
303
+ }
304
+ function buildUserKeyDirectoryTransparencyEntry(params) {
305
+ const entryWithoutHash = {
306
+ orgId: params.checkpoint.orgId,
307
+ version: params.checkpoint.version,
308
+ directoryCheckpointPayload: buildUserKeyDirectoryCheckpointPayload(params.checkpoint),
309
+ signerUserKeyPairId: params.signerUserKeyPairId,
310
+ signerOrgUserId: params.signerOrgUserId,
311
+ signerFingerprint: getPublicKeyFingerprint(params.signerPublicKey),
312
+ signature: params.signature,
313
+ previousEntryHash: params.previousEntryHash ?? null
314
+ };
315
+ return {
316
+ ...entryWithoutHash,
317
+ entryHash: buildUserKeyDirectoryTransparencyEntryHash(entryWithoutHash)
318
+ };
319
+ }
320
+ function verifyUserKeyDirectoryTransparencyProof(params) {
321
+ if (params.proof.head.version !== params.currentEntry.version || params.proof.head.hash !== params.currentEntry.entryHash) {
322
+ return false;
323
+ }
324
+ if (!params.previousHead) {
325
+ if (params.proof.entries.length === 0) {
326
+ return false;
327
+ }
328
+ for (let index = 0; index < params.proof.entries.length; index++) {
329
+ const entry = params.proof.entries[index];
330
+ if (!entry) {
331
+ return false;
332
+ }
333
+ const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
334
+ orgId: entry.orgId,
335
+ version: entry.version,
336
+ directoryCheckpointPayload: entry.directoryCheckpointPayload,
337
+ signerUserKeyPairId: entry.signerUserKeyPairId,
338
+ signerOrgUserId: entry.signerOrgUserId,
339
+ signerFingerprint: entry.signerFingerprint,
340
+ signature: entry.signature,
341
+ previousEntryHash: entry.previousEntryHash
342
+ });
343
+ if (expectedHash !== entry.entryHash) {
344
+ return false;
345
+ }
346
+ if (index === 0) {
347
+ continue;
348
+ }
349
+ const previousEntry = params.proof.entries[index - 1];
350
+ if (!previousEntry) {
351
+ return false;
352
+ }
353
+ if (entry.previousEntryHash !== previousEntry.entryHash || entry.version !== previousEntry.version + 1) {
354
+ return false;
355
+ }
356
+ }
357
+ const lastEntry = params.proof.entries[params.proof.entries.length - 1];
358
+ return lastEntry?.entryHash === params.currentEntry.entryHash && lastEntry.version === params.currentEntry.version;
359
+ }
360
+ if (params.currentEntry.version < params.previousHead.version) {
361
+ return false;
362
+ }
363
+ if (params.currentEntry.version === params.previousHead.version) {
364
+ return params.currentEntry.entryHash === params.previousHead.hash && params.proof.entries.length === 0;
365
+ }
366
+ if (params.proof.entries.length === 0) {
367
+ return false;
368
+ }
369
+ let previousVersion = params.previousHead.version;
370
+ let previousHash = params.previousHead.hash;
371
+ for (const entry of params.proof.entries) {
372
+ const expectedHash = buildUserKeyDirectoryTransparencyEntryHash({
373
+ orgId: entry.orgId,
374
+ version: entry.version,
375
+ directoryCheckpointPayload: entry.directoryCheckpointPayload,
376
+ signerUserKeyPairId: entry.signerUserKeyPairId,
377
+ signerOrgUserId: entry.signerOrgUserId,
378
+ signerFingerprint: entry.signerFingerprint,
379
+ signature: entry.signature,
380
+ previousEntryHash: entry.previousEntryHash
381
+ });
382
+ if (expectedHash !== entry.entryHash || entry.previousEntryHash !== previousHash || entry.version !== previousVersion + 1) {
383
+ return false;
384
+ }
385
+ previousVersion = entry.version;
386
+ previousHash = entry.entryHash;
387
+ }
388
+ return previousHash === params.currentEntry.entryHash && previousVersion === params.currentEntry.version;
389
+ }
390
+ function buildWrappedDekSignaturePayload(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek) {
391
+ return [
392
+ WRAPPED_DEK_SIGNATURE_PREFIX,
393
+ vaultId,
394
+ recipientKeyId,
395
+ signerUserKeyPairId,
396
+ String(dekVersion),
397
+ getWrappedDekFingerprint(wrappedDek)
398
+ ].join(":");
399
+ }
400
+ function verifyWrappedDekSignature(vaultId, recipientKeyId, signerUserKeyPairId, dekVersion, wrappedDek, wrappedDekSignature, signerPublicKeyPem) {
401
+ const payload = buildWrappedDekSignaturePayload(
402
+ vaultId,
403
+ recipientKeyId,
404
+ signerUserKeyPairId,
405
+ dekVersion,
406
+ wrappedDek
407
+ );
408
+ try {
409
+ return import_node_crypto.default.verify(
410
+ "sha256",
411
+ Buffer.from(payload, "utf8"),
412
+ {
413
+ key: signerPublicKeyPem,
414
+ ...RSA_PSS_SIGN_CONFIG
415
+ },
416
+ Buffer.from(wrappedDekSignature, "base64")
417
+ );
418
+ } catch {
419
+ return false;
420
+ }
421
+ }
422
+ function normalizeVaultSummaryCheckpoint(checkpoint) {
423
+ return {
424
+ vaultId: checkpoint.vaultId,
425
+ version: checkpoint.version,
426
+ name: checkpoint.name,
427
+ dataClassification: checkpoint.dataClassification ?? null,
428
+ currentDekVersion: checkpoint.currentDekVersion ?? null,
429
+ items: [...checkpoint.items].map((item) => ({
430
+ id: item.id,
431
+ name: item.name,
432
+ type: item.type ?? null,
433
+ websites: [...item.websites],
434
+ groupId: item.groupId ?? null
435
+ })).sort((left, right) => left.id.localeCompare(right.id)),
436
+ groups: [...checkpoint.groups].map((group) => ({
437
+ id: group.id,
438
+ name: group.name,
439
+ parentId: group.parentId ?? null
440
+ })).sort((left, right) => left.id.localeCompare(right.id))
441
+ };
442
+ }
443
+ function canonicalizeVaultSummaryCheckpoint(checkpoint) {
444
+ return JSON.stringify(normalizeVaultSummaryCheckpoint(checkpoint));
445
+ }
446
+ function buildVaultSummaryCheckpointPayload(checkpoint) {
447
+ return getCheckpointFingerprint(
448
+ VAULT_SUMMARY_CHECKPOINT_PREFIX,
449
+ canonicalizeVaultSummaryCheckpoint(checkpoint)
450
+ );
451
+ }
452
+ function verifyVaultSummaryCheckpoint(checkpoint, signature, publicKeyPem) {
453
+ try {
454
+ return import_node_crypto.default.verify(
455
+ "sha256",
456
+ Buffer.from(buildVaultSummaryCheckpointPayload(checkpoint), "utf8"),
457
+ {
458
+ key: publicKeyPem,
459
+ ...RSA_PSS_SIGN_CONFIG
460
+ },
461
+ Buffer.from(signature, "base64")
462
+ );
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+ function normalizeVaultItemDetailCheckpoint(checkpoint) {
468
+ return {
469
+ vaultItemId: checkpoint.vaultItemId,
470
+ vaultId: checkpoint.vaultId,
471
+ version: checkpoint.version,
472
+ name: checkpoint.name,
473
+ type: checkpoint.type ?? null,
474
+ websites: [...checkpoint.websites],
475
+ groupId: checkpoint.groupId ?? null,
476
+ fields: [...checkpoint.fields].map((field) => ({
477
+ id: field.id,
478
+ name: field.name,
479
+ type: field.type,
480
+ order: field.order,
481
+ fieldInstanceIds: [...field.fieldInstanceIds].sort(),
482
+ assetIds: [...field.assetIds].sort()
483
+ })).sort((left, right) => left.order - right.order || left.id.localeCompare(right.id))
484
+ };
485
+ }
486
+ function canonicalizeVaultItemDetailCheckpoint(checkpoint) {
487
+ return JSON.stringify(normalizeVaultItemDetailCheckpoint(checkpoint));
488
+ }
489
+ function buildVaultItemDetailCheckpointPayload(checkpoint) {
490
+ return getCheckpointFingerprint(
491
+ VAULT_ITEM_DETAIL_CHECKPOINT_PREFIX,
492
+ canonicalizeVaultItemDetailCheckpoint(checkpoint)
493
+ );
494
+ }
495
+ function verifyVaultItemDetailCheckpoint(checkpoint, signature, publicKeyPem) {
496
+ try {
497
+ return import_node_crypto.default.verify(
498
+ "sha256",
499
+ Buffer.from(buildVaultItemDetailCheckpointPayload(checkpoint), "utf8"),
500
+ {
501
+ key: publicKeyPem,
502
+ ...RSA_PSS_SIGN_CONFIG
503
+ },
504
+ Buffer.from(signature, "base64")
505
+ );
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+ function isVaultEnvelope(value) {
511
+ if (!value.startsWith("{")) {
512
+ return false;
513
+ }
514
+ try {
515
+ const parsed = JSON.parse(value);
516
+ return parsed.v === 3;
517
+ } catch {
518
+ return false;
519
+ }
520
+ }
521
+ function decryptWithVaultDEK(encryptedValue, dek) {
522
+ if (!isVaultEnvelope(encryptedValue)) {
523
+ throw new Error("Invalid encrypted value: expected v3 vault envelope format");
524
+ }
525
+ const envelope = JSON.parse(encryptedValue);
526
+ const decipher = import_node_crypto.default.createDecipheriv("aes-256-gcm", dek, Buffer.from(envelope.iv, "base64"));
527
+ decipher.setAuthTag(Buffer.from(envelope.t, "base64"));
528
+ const decrypted = Buffer.concat([
529
+ decipher.update(Buffer.from(envelope.d, "base64")),
530
+ decipher.final()
531
+ ]);
532
+ return decrypted.toString("utf8");
533
+ }
534
+ function decryptStoredFieldValue(value, dek) {
535
+ return isVaultEnvelope(value) ? decryptWithVaultDEK(value, dek) : value;
536
+ }
537
+ function unwrapDEKWithPrivateKey(wrappedDek, privateKeyPem) {
538
+ return import_node_crypto.default.privateDecrypt(
539
+ { key: privateKeyPem, ...RSA_OAEP_CONFIG },
540
+ Buffer.from(wrappedDek, "base64")
541
+ );
542
+ }
543
+
544
+ // src/trust-store.ts
545
+ var import_node_fs2 = __toESM(require("fs"), 1);
546
+ var import_node_path2 = __toESM(require("path"), 1);
547
+ function loadTrustStore(trustStorePath) {
548
+ try {
549
+ const raw = import_node_fs2.default.readFileSync(trustStorePath, "utf8");
550
+ const parsed = JSON.parse(raw);
551
+ return {
552
+ version: 1,
553
+ userKeyPins: parsed.userKeyPins ?? {},
554
+ checkpointVersionPins: parsed.checkpointVersionPins ?? {},
555
+ transparencyHeadPins: parsed.transparencyHeadPins ?? {}
556
+ };
557
+ } catch {
558
+ return {
559
+ version: 1,
560
+ userKeyPins: {},
561
+ checkpointVersionPins: {},
562
+ transparencyHeadPins: {}
563
+ };
564
+ }
565
+ }
566
+ function saveTrustStore(trustStorePath, store) {
567
+ import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(trustStorePath), { recursive: true });
568
+ import_node_fs2.default.writeFileSync(trustStorePath, JSON.stringify(store, null, 2) + "\n", "utf8");
569
+ }
570
+ function getPinStorageKey(orgId, orgUserId) {
571
+ return `${orgId}:${orgUserId}`;
572
+ }
573
+ function getDirectoryPinStorageKey(orgId) {
574
+ return `org:${orgId}`;
575
+ }
576
+ async function fetchWitnessArtifact(pathName) {
577
+ const response = await fetch(
578
+ buildTransparencyWitnessUrl(DEFAULT_TRANSPARENCY_WITNESS_URL, pathName),
579
+ {
580
+ cache: "no-store"
581
+ }
582
+ );
583
+ if (!response.ok) {
584
+ throw new Error(`Failed to fetch public transparency witness artifact (${response.status}).`);
585
+ }
586
+ return response.json();
587
+ }
588
+ function getSinglePinnedTransparencyHead(trustStorePath) {
589
+ const store = loadTrustStore(trustStorePath);
590
+ const heads = Object.values(store.transparencyHeadPins);
591
+ return heads.length === 1 ? heads[0] : null;
592
+ }
593
+ async function getPublicOrgWitnessHead(apiBaseUrl, orgId) {
594
+ if (!shouldUseDefaultTransparencyWitness(apiBaseUrl)) {
595
+ return null;
596
+ }
597
+ const artifact = await fetchWitnessArtifact(
598
+ buildOrgUserKeyDirectoryWitnessPath(orgId)
599
+ );
600
+ if (artifact.kind !== "org-user-key-directory" || artifact.orgId !== orgId || !verifyOrgUserKeyDirectoryWitnessArtifact(
601
+ artifact,
602
+ TRANSPARENCY_WITNESS_ROOT_PUBLIC_KEY_PEM
603
+ )) {
604
+ throw new Error(`Public transparency witness verification failed for org ${orgId}.`);
605
+ }
606
+ return artifact.head;
607
+ }
608
+ async function pinVaultUserPublicKeys(trustStorePath, orgId, publicKeys) {
609
+ const store = loadTrustStore(trustStorePath);
610
+ let changed = false;
611
+ for (const key of publicKeys) {
612
+ const storageKey = getPinStorageKey(orgId, key.orgUserId);
613
+ const computedFingerprint = getPublicKeyFingerprint(key.publicKey);
614
+ if (computedFingerprint !== key.fingerprint) {
615
+ throw new Error(`Server returned a mismatched fingerprint for user ${key.orgUserId}.`);
616
+ }
617
+ const existing = store.userKeyPins[storageKey];
618
+ const verifiedAt = (/* @__PURE__ */ new Date()).toISOString();
619
+ if (!existing) {
620
+ store.userKeyPins[storageKey] = {
621
+ keyPairId: key.userKeyPairId,
622
+ fingerprint: key.fingerprint,
623
+ publicKey: key.publicKey,
624
+ pinnedAt: verifiedAt,
625
+ verifiedAt
626
+ };
627
+ changed = true;
628
+ continue;
629
+ }
630
+ if (existing.keyPairId === key.userKeyPairId) {
631
+ if (existing.fingerprint !== key.fingerprint || existing.publicKey !== key.publicKey) {
632
+ throw new Error(
633
+ `Pinned public key ${key.userKeyPairId} changed unexpectedly for user ${key.orgUserId}.`
634
+ );
635
+ }
636
+ if (existing.verifiedAt !== verifiedAt) {
637
+ store.userKeyPins[storageKey] = {
638
+ ...existing,
639
+ verifiedAt
640
+ };
641
+ changed = true;
642
+ }
643
+ continue;
644
+ }
645
+ if (!key.previousUserKeyPairId || key.previousUserKeyPairId !== existing.keyPairId || !key.rotationSignature) {
646
+ throw new Error(`Public key rotation for user ${key.orgUserId} is missing a trusted continuity proof.`);
647
+ }
648
+ const rotationVerified = verifyUserKeyRotation(
649
+ existing.keyPairId,
650
+ key.publicKey,
651
+ key.rotationSignature,
652
+ existing.publicKey
653
+ );
654
+ if (!rotationVerified) {
655
+ throw new Error(`Public key rotation for user ${key.orgUserId} failed signature verification.`);
656
+ }
657
+ store.userKeyPins[storageKey] = {
658
+ keyPairId: key.userKeyPairId,
659
+ fingerprint: key.fingerprint,
660
+ publicKey: key.publicKey,
661
+ pinnedAt: existing.pinnedAt,
662
+ verifiedAt
663
+ };
664
+ changed = true;
665
+ }
666
+ if (changed) {
667
+ saveTrustStore(trustStorePath, store);
668
+ }
669
+ return publicKeys;
670
+ }
671
+ async function verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead) {
672
+ if (!directory.directoryCheckpoint) {
673
+ if (directory.publicKeys.length > 0) {
674
+ throw new Error("Server omitted the user-key directory checkpoint for a non-empty vault signer directory.");
675
+ }
676
+ return null;
677
+ }
678
+ const { directoryCheckpoint } = directory;
679
+ const orgId = directoryCheckpoint.checkpoint.orgId;
680
+ if (!directoryCheckpoint.signerOrgUserId || !directoryCheckpoint.signerPublicKey) {
681
+ throw new Error("Server returned an incomplete user-key directory signer payload.");
682
+ }
683
+ const signerFingerprint = getPublicKeyFingerprint(directoryCheckpoint.signerPublicKey);
684
+ const signerEntry = directoryCheckpoint.checkpoint.entries.find(
685
+ (entry) => entry.userKeyPairId === directoryCheckpoint.signerUserKeyPairId && entry.orgUserId === directoryCheckpoint.signerOrgUserId
686
+ );
687
+ const signerKey = {
688
+ userKeyPairId: directoryCheckpoint.signerUserKeyPairId,
689
+ orgUserId: directoryCheckpoint.signerOrgUserId,
690
+ publicKey: directoryCheckpoint.signerPublicKey,
691
+ fingerprint: signerFingerprint,
692
+ previousUserKeyPairId: signerEntry?.previousUserKeyPairId ?? null,
693
+ rotationSignature: signerEntry?.rotationSignature ?? null
694
+ };
695
+ const storeBeforeVerification = loadTrustStore(trustStorePath);
696
+ try {
697
+ await pinVaultUserPublicKeys(trustStorePath, orgId, [signerKey]);
698
+ const verified = verifyUserKeyDirectoryCheckpoint(
699
+ directoryCheckpoint.checkpoint,
700
+ directoryCheckpoint.signature,
701
+ directoryCheckpoint.signerPublicKey
702
+ );
703
+ if (!verified) {
704
+ throw new Error(`User-key directory signature verification failed for org ${orgId}.`);
705
+ }
706
+ if (!directory.transparency) {
707
+ throw new Error(`Server omitted the user-key directory transparency proof for org ${orgId}.`);
708
+ }
709
+ const store = loadTrustStore(trustStorePath);
710
+ const pinnedTransparencyHead = store.transparencyHeadPins[getDirectoryPinStorageKey(orgId)] ?? null;
711
+ const trustedPreviousHead = anchorHead ?? pinnedTransparencyHead;
712
+ const legacyPinnedVersion = store.checkpointVersionPins[getDirectoryPinStorageKey(orgId)] ?? null;
713
+ if (!trustedPreviousHead && legacyPinnedVersion !== null && directory.transparency.head.version < legacyPinnedVersion) {
714
+ throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
715
+ }
716
+ if (!trustedPreviousHead && directory.transparency.entries.length === 0) {
717
+ throw new Error(`Server omitted the current transparency entry for org ${orgId}.`);
718
+ }
719
+ if (directory.transparency.entries.length > 0) {
720
+ const currentProofEntry = directory.transparency.entries[directory.transparency.entries.length - 1];
721
+ if (!currentProofEntry) {
722
+ throw new Error(`Server returned an empty transparency proof for org ${orgId}.`);
723
+ }
724
+ const expectedCurrentEntry = buildUserKeyDirectoryTransparencyEntry({
725
+ checkpoint: directoryCheckpoint.checkpoint,
726
+ signerUserKeyPairId: directoryCheckpoint.signerUserKeyPairId,
727
+ signerOrgUserId: directoryCheckpoint.signerOrgUserId,
728
+ signerPublicKey: directoryCheckpoint.signerPublicKey,
729
+ signature: directoryCheckpoint.signature,
730
+ previousEntryHash: currentProofEntry.previousEntryHash ?? null
731
+ });
732
+ if (expectedCurrentEntry.entryHash !== currentProofEntry.entryHash) {
733
+ throw new Error(`User-key transparency entry does not match the signed directory for org ${orgId}.`);
734
+ }
735
+ if (anchorHead && directory.transparency.head.version === anchorHead.version) {
736
+ if (directory.transparency.head.hash !== anchorHead.hash) {
737
+ throw new Error(`Public transparency witness head fork detected for org ${orgId}.`);
738
+ }
739
+ if (currentProofEntry.entryHash !== anchorHead.hash || expectedCurrentEntry.entryHash !== anchorHead.hash) {
740
+ throw new Error(`User-key transparency witness anchor mismatch for org ${orgId}.`);
741
+ }
742
+ } else if (!verifyUserKeyDirectoryTransparencyProof({
743
+ currentEntry: expectedCurrentEntry,
744
+ proof: directory.transparency,
745
+ previousHead: trustedPreviousHead
746
+ })) {
747
+ throw new Error(`User-key transparency proof verification failed for org ${orgId}.`);
748
+ }
749
+ } else if (anchorHead && directory.transparency.head.version === anchorHead.version) {
750
+ throw new Error(`Server omitted the current transparency entry required to verify org ${orgId} against the public witness.`);
751
+ } else if (!trustedPreviousHead || trustedPreviousHead.version !== directory.transparency.head.version || trustedPreviousHead.hash !== directory.transparency.head.hash) {
752
+ throw new Error(`Server returned an incomplete user-key transparency proof for org ${orgId}.`);
753
+ }
754
+ assertAndPinTransparencyHead(trustStorePath, orgId, directory.transparency.head);
755
+ const checkpointEntries = new Map(
756
+ directoryCheckpoint.checkpoint.entries.map((entry) => [entry.userKeyPairId, entry])
757
+ );
758
+ for (const key of directory.publicKeys) {
759
+ const entry = checkpointEntries.get(key.userKeyPairId);
760
+ if (!entry) {
761
+ throw new Error(`User key ${key.userKeyPairId} is missing from the signed org directory.`);
762
+ }
763
+ if (entry.orgUserId !== key.orgUserId || entry.fingerprint !== key.fingerprint || entry.previousUserKeyPairId !== key.previousUserKeyPairId || entry.rotationSignature !== key.rotationSignature) {
764
+ throw new Error(`User key ${key.userKeyPairId} does not match the signed org directory.`);
765
+ }
766
+ }
767
+ return orgId;
768
+ } catch (error) {
769
+ saveTrustStore(trustStorePath, storeBeforeVerification);
770
+ throw error;
771
+ }
772
+ }
773
+ async function verifyAndPinVaultUserPublicKeys(trustStorePath, directory, anchorHead) {
774
+ const orgId = await verifySignedUserKeyDirectory(trustStorePath, directory, anchorHead);
775
+ if (!orgId) {
776
+ return directory.publicKeys;
777
+ }
778
+ return pinVaultUserPublicKeys(trustStorePath, orgId, directory.publicKeys);
779
+ }
780
+ function assertAndPinCheckpointVersion(trustStorePath, storageKey, version) {
781
+ const store = loadTrustStore(trustStorePath);
782
+ const pinnedVersion = store.checkpointVersionPins[storageKey];
783
+ if (pinnedVersion !== void 0 && version < pinnedVersion) {
784
+ throw new Error(`Checkpoint version rolled back unexpectedly for ${storageKey}.`);
785
+ }
786
+ if (pinnedVersion === void 0 || version > pinnedVersion) {
787
+ store.checkpointVersionPins[storageKey] = version;
788
+ saveTrustStore(trustStorePath, store);
789
+ }
790
+ }
791
+ function assertAndPinTransparencyHead(trustStorePath, orgId, head) {
792
+ const store = loadTrustStore(trustStorePath);
793
+ const storageKey = getDirectoryPinStorageKey(orgId);
794
+ const pinnedHead = store.transparencyHeadPins[storageKey];
795
+ if (pinnedHead) {
796
+ if (head.version < pinnedHead.version) {
797
+ throw new Error(`User-key transparency head rolled back unexpectedly for org ${orgId}.`);
798
+ }
799
+ if (head.version === pinnedHead.version && head.hash !== pinnedHead.hash) {
800
+ throw new Error(`User-key transparency head fork detected for org ${orgId}.`);
801
+ }
802
+ }
803
+ if (!pinnedHead || head.version > pinnedHead.version) {
804
+ store.transparencyHeadPins[storageKey] = head;
805
+ saveTrustStore(trustStorePath, store);
806
+ }
807
+ assertAndPinCheckpointVersion(trustStorePath, storageKey, head.version);
808
+ }
809
+
810
+ // src/index.ts
811
+ var R4_DEFAULT_API_BASE_URL = "https://r4.dev";
812
+ var R4_DEV_API_BASE_URL = "https://dev.r4.dev";
813
+ function toScreamingSnakeCase(input) {
814
+ return input.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toUpperCase();
815
+ }
816
+ function resolveTrustStorePath(config) {
817
+ if (config.trustStorePath) {
818
+ return import_node_path3.default.resolve(config.trustStorePath);
819
+ }
820
+ if (config.privateKeyPath) {
821
+ return `${import_node_path3.default.resolve(config.privateKeyPath)}.trust.json`;
822
+ }
823
+ return import_node_path3.default.resolve(process.cwd(), ".r4-trust-store.json");
824
+ }
825
+ function resolveApiBaseUrl(config) {
826
+ if (config.baseUrl) {
827
+ return config.baseUrl;
828
+ }
829
+ return config.dev ? R4_DEV_API_BASE_URL : R4_DEFAULT_API_BASE_URL;
830
+ }
831
+ function buildVaultSummaryCheckpointFromListResponse(response, version) {
832
+ return {
833
+ vaultId: response.vaultId,
834
+ version,
835
+ name: response.vaultName,
836
+ dataClassification: response.dataClassification ?? null,
837
+ currentDekVersion: response.currentDekVersion ?? null,
838
+ items: response.items.map((item) => ({
839
+ id: item.id,
840
+ name: item.name,
841
+ type: item.type ?? null,
842
+ websites: item.websites ?? [],
843
+ groupId: item.groupId ?? null
844
+ })),
845
+ groups: response.vaultItemGroups.map((group) => ({
846
+ id: group.id,
847
+ name: group.name,
848
+ parentId: group.parentId ?? null
849
+ }))
850
+ };
851
+ }
852
+ function buildVaultItemDetailCheckpointFromResponse(item, version) {
853
+ return {
854
+ vaultItemId: item.id,
855
+ vaultId: item.vaultId,
856
+ version,
857
+ name: item.name,
858
+ type: item.type ?? null,
859
+ websites: item.websites ?? [],
860
+ groupId: item.groupId ?? null,
861
+ fields: item.fields.map((field, index) => ({
862
+ id: field.id,
863
+ name: field.name,
864
+ type: field.type,
865
+ order: field.order ?? index,
866
+ fieldInstanceIds: field.fieldInstanceIds ?? [],
867
+ assetIds: field.assetIds ?? []
868
+ }))
869
+ };
870
+ }
871
+ var R4 = class _R4 {
872
+ client;
873
+ baseUrl;
874
+ projectId;
875
+ privateKeyPem;
876
+ publicKeyPem;
877
+ trustStorePath;
878
+ _env = null;
879
+ constructor(config) {
880
+ if (!config.apiKey) {
881
+ throw new Error("R4 SDK: apiKey is required");
882
+ }
883
+ if (!config.privateKey && !config.privateKeyPath) {
884
+ throw new Error(
885
+ "R4 SDK: privateKey or privateKeyPath is required for zero-trust local decryption."
886
+ );
887
+ }
888
+ const baseUrl = resolveApiBaseUrl(config);
889
+ this.baseUrl = baseUrl;
890
+ this.client = new R4Client(config.apiKey, baseUrl);
891
+ this.projectId = config.projectId;
892
+ this.privateKeyPem = config.privateKey ?? loadPrivateKey(config.privateKeyPath);
893
+ this.publicKeyPem = derivePublicKey(this.privateKeyPem);
894
+ this.trustStorePath = resolveTrustStorePath(config);
895
+ }
896
+ /**
897
+ * Static factory method that creates and initializes an R4 instance.
898
+ */
899
+ static async create(config) {
900
+ const instance = new _R4(config);
901
+ await instance.init();
902
+ return instance;
903
+ }
904
+ /**
905
+ * Initializes the SDK by registering the agent public key (idempotent) and
906
+ * decrypting all accessible vault values locally into a flat env map.
907
+ */
908
+ async init() {
909
+ this._env = await this.fetchEnv();
910
+ }
911
+ /**
912
+ * Returns the locally decrypted env map.
913
+ */
914
+ get env() {
915
+ if (!this._env) {
916
+ throw new Error(
917
+ "R4 SDK: env is not initialized. Call await r4.init() first, or use R4.create() for automatic initialization."
918
+ );
919
+ }
920
+ return this._env;
921
+ }
922
+ /**
923
+ * Re-fetches and locally re-decrypts the current vault view.
924
+ */
925
+ async refresh() {
926
+ this._env = await this.fetchEnv();
927
+ }
928
+ /**
929
+ * Registers the local agent public key, loads all accessible vaults, verifies
930
+ * wrapped-DEK signatures against pinned signer keys, unwraps each vault DEK,
931
+ * and builds a flat SCREAMING_SNAKE_CASE env map from decrypted field values.
932
+ */
933
+ async fetchEnv() {
934
+ try {
935
+ await this.client.registerAgentPublicKey({
936
+ publicKey: this.publicKeyPem
937
+ });
938
+ } catch (error) {
939
+ throw new Error(
940
+ `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)}`
941
+ );
942
+ }
943
+ const { vaults } = await this.client.listVaults(this.projectId);
944
+ const envEntries = await Promise.all(vaults.map((vault) => this.fetchVaultEnv(vault.id)));
945
+ return Object.assign({}, ...envEntries);
946
+ }
947
+ /**
948
+ * Fetches a single vault's wrapped DEK, verifies it against the pinned signer
949
+ * directory, unwraps the DEK locally, then decrypts every field value in that
950
+ * vault item-by-item.
951
+ */
952
+ async fetchVaultEnv(vaultId) {
953
+ const pinnedTransparencyHead = getSinglePinnedTransparencyHead(this.trustStorePath);
954
+ const [wrappedKey, itemsResponse, initialPublicKeyDirectory] = await Promise.all([
955
+ this.client.getAgentWrappedKey(vaultId),
956
+ this.client.listVaultItems(vaultId),
957
+ this.client.getVaultUserKeyDirectory(
958
+ vaultId,
959
+ pinnedTransparencyHead ? {
960
+ knownTransparencyVersion: pinnedTransparencyHead.version,
961
+ knownTransparencyHash: pinnedTransparencyHead.hash
962
+ } : void 0
963
+ )
964
+ ]);
965
+ let publicKeyDirectory = initialPublicKeyDirectory;
966
+ let witnessAnchorHead = null;
967
+ if (!pinnedTransparencyHead) {
968
+ const orgId = initialPublicKeyDirectory.directoryCheckpoint?.checkpoint.orgId ?? null;
969
+ if (orgId && initialPublicKeyDirectory.directoryCheckpoint && initialPublicKeyDirectory.transparency) {
970
+ witnessAnchorHead = await getPublicOrgWitnessHead(this.baseUrl, orgId);
971
+ if (witnessAnchorHead) {
972
+ if (initialPublicKeyDirectory.transparency.head.version < witnessAnchorHead.version) {
973
+ throw new Error(`R4 SDK: public transparency witness head is ahead of the server response for org ${orgId}.`);
974
+ }
975
+ if (initialPublicKeyDirectory.transparency.head.version === witnessAnchorHead.version) {
976
+ if (initialPublicKeyDirectory.transparency.head.hash !== witnessAnchorHead.hash) {
977
+ throw new Error(`R4 SDK: public transparency witness head fork detected for org ${orgId}.`);
978
+ }
979
+ } else {
980
+ publicKeyDirectory = await this.client.getVaultUserKeyDirectory(vaultId, {
981
+ knownTransparencyVersion: witnessAnchorHead.version,
982
+ knownTransparencyHash: witnessAnchorHead.hash
983
+ });
984
+ }
985
+ }
986
+ }
987
+ }
988
+ const trustedPublicKeys = await verifyAndPinVaultUserPublicKeys(
989
+ this.trustStorePath,
990
+ publicKeyDirectory,
991
+ witnessAnchorHead
992
+ );
993
+ const signerKey = trustedPublicKeys.find(
994
+ (publicKey) => publicKey.userKeyPairId === wrappedKey.signerUserKeyPairId
995
+ );
996
+ if (!signerKey) {
997
+ throw new Error(
998
+ `R4 SDK: wrapped DEK for vault ${vaultId} was signed by unknown user key ${wrappedKey.signerUserKeyPairId}.`
999
+ );
1000
+ }
1001
+ const signatureVerified = verifyWrappedDekSignature(
1002
+ vaultId,
1003
+ wrappedKey.encryptionKeyId,
1004
+ wrappedKey.signerUserKeyPairId,
1005
+ wrappedKey.dekVersion,
1006
+ wrappedKey.wrappedDek,
1007
+ wrappedKey.wrappedDekSignature,
1008
+ signerKey.publicKey
1009
+ );
1010
+ if (!signatureVerified) {
1011
+ throw new Error(`R4 SDK: wrapped DEK signature verification failed for vault ${vaultId}.`);
1012
+ }
1013
+ const dek = unwrapDEKWithPrivateKey(wrappedKey.wrappedDek, this.privateKeyPem);
1014
+ if (!itemsResponse.summaryCheckpoint) {
1015
+ throw new Error(`R4 SDK: vault ${vaultId} is missing a signed summary checkpoint.`);
1016
+ }
1017
+ const summarySignerKey = trustedPublicKeys.find(
1018
+ (publicKey) => publicKey.userKeyPairId === itemsResponse.summaryCheckpoint.signerUserKeyPairId
1019
+ );
1020
+ if (!summarySignerKey) {
1021
+ throw new Error(
1022
+ `R4 SDK: vault ${vaultId} summary checkpoint was signed by unknown user key ${itemsResponse.summaryCheckpoint.signerUserKeyPairId}.`
1023
+ );
1024
+ }
1025
+ const expectedSummaryCheckpoint = buildVaultSummaryCheckpointFromListResponse(
1026
+ itemsResponse,
1027
+ itemsResponse.summaryCheckpoint.checkpoint.version
1028
+ );
1029
+ const summaryVerified = verifyVaultSummaryCheckpoint(
1030
+ expectedSummaryCheckpoint,
1031
+ itemsResponse.summaryCheckpoint.signature,
1032
+ summarySignerKey.publicKey
1033
+ );
1034
+ if (!summaryVerified) {
1035
+ throw new Error(`R4 SDK: vault summary checkpoint verification failed for vault ${vaultId}.`);
1036
+ }
1037
+ assertAndPinCheckpointVersion(
1038
+ this.trustStorePath,
1039
+ `summary:${vaultId}`,
1040
+ expectedSummaryCheckpoint.version
1041
+ );
1042
+ const itemDetails = await Promise.all(
1043
+ itemsResponse.items.map((item) => this.client.getVaultItemDetail(vaultId, item.id))
1044
+ );
1045
+ const env = {};
1046
+ for (const item of itemDetails) {
1047
+ if (!item.detailCheckpoint) {
1048
+ throw new Error(`R4 SDK: vault item ${item.id} is missing a signed detail checkpoint.`);
1049
+ }
1050
+ const detailSignerKey = trustedPublicKeys.find(
1051
+ (publicKey) => publicKey.userKeyPairId === item.detailCheckpoint.signerUserKeyPairId
1052
+ );
1053
+ if (!detailSignerKey) {
1054
+ throw new Error(
1055
+ `R4 SDK: vault item ${item.id} checkpoint was signed by unknown user key ${item.detailCheckpoint.signerUserKeyPairId}.`
1056
+ );
1057
+ }
1058
+ const expectedDetailCheckpoint = buildVaultItemDetailCheckpointFromResponse(
1059
+ item,
1060
+ item.detailCheckpoint.checkpoint.version
1061
+ );
1062
+ const detailVerified = verifyVaultItemDetailCheckpoint(
1063
+ expectedDetailCheckpoint,
1064
+ item.detailCheckpoint.signature,
1065
+ detailSignerKey.publicKey
1066
+ );
1067
+ if (!detailVerified) {
1068
+ throw new Error(`R4 SDK: vault item checkpoint verification failed for item ${item.id}.`);
1069
+ }
1070
+ assertAndPinCheckpointVersion(
1071
+ this.trustStorePath,
1072
+ `detail:${item.id}`,
1073
+ expectedDetailCheckpoint.version
1074
+ );
1075
+ for (const field of item.fields) {
1076
+ if (field.value === null) {
1077
+ continue;
1078
+ }
1079
+ env[toScreamingSnakeCase(`${item.name}_${field.name}`)] = decryptStoredFieldValue(
1080
+ field.value,
1081
+ dek
1082
+ );
1083
+ }
1084
+ }
1085
+ return env;
1086
+ }
1087
+ };
1088
+ var src_default = R4;
1089
+ // Annotate the CommonJS export names for ESM import in node:
1090
+ 0 && (module.exports = {
1091
+ R4
1092
+ });
1093
+ //# sourceMappingURL=index.cjs.map