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