@efsf/typescript 0.1.0

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/dist/index.mjs ADDED
@@ -0,0 +1,1322 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/certificate.ts
9
+ import * as crypto from "crypto";
10
+ import { v4 as uuidv4 } from "uuid";
11
+
12
+ // src/exceptions.ts
13
+ var EFSFError = class extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "EFSFError";
17
+ Object.setPrototypeOf(this, new.target.prototype);
18
+ }
19
+ };
20
+ var RecordNotFoundError = class extends EFSFError {
21
+ constructor(recordId, message) {
22
+ super(message ?? `Record not found: ${recordId}`);
23
+ this.recordId = recordId;
24
+ this.name = "RecordNotFoundError";
25
+ }
26
+ };
27
+ var RecordExpiredError = class extends EFSFError {
28
+ constructor(recordId, expiredAt) {
29
+ const msg = expiredAt ? `Record expired: ${recordId} (expired at ${expiredAt})` : `Record expired: ${recordId}`;
30
+ super(msg);
31
+ this.recordId = recordId;
32
+ this.expiredAt = expiredAt;
33
+ this.name = "RecordExpiredError";
34
+ }
35
+ };
36
+ var CryptoError = class extends EFSFError {
37
+ constructor(operation, message) {
38
+ super(message ?? `Cryptographic operation failed: ${operation}`);
39
+ this.operation = operation;
40
+ this.name = "CryptoError";
41
+ }
42
+ };
43
+ var AttestationError = class extends EFSFError {
44
+ constructor(message) {
45
+ super(message ?? "Attestation failed");
46
+ this.name = "AttestationError";
47
+ }
48
+ };
49
+ var BackendError = class extends EFSFError {
50
+ constructor(backend, message) {
51
+ super(message ?? `Backend error: ${backend}`);
52
+ this.backend = backend;
53
+ this.name = "BackendError";
54
+ }
55
+ };
56
+ var ValidationError = class extends EFSFError {
57
+ constructor(field, message) {
58
+ super(message ?? `Validation error: ${field}`);
59
+ this.field = field;
60
+ this.name = "ValidationError";
61
+ }
62
+ };
63
+ var TTLViolationError = class extends EFSFError {
64
+ constructor(recordId, expectedTTL, actualTTL) {
65
+ const msg = actualTTL ? `TTL violation for record ${recordId}: expected ${expectedTTL}, got ${actualTTL}` : `TTL violation for record ${recordId}: expected ${expectedTTL}`;
66
+ super(msg);
67
+ this.recordId = recordId;
68
+ this.expectedTTL = expectedTTL;
69
+ this.actualTTL = actualTTL;
70
+ this.name = "TTLViolationError";
71
+ }
72
+ };
73
+
74
+ // src/certificate.ts
75
+ var DestructionMethod = /* @__PURE__ */ ((DestructionMethod2) => {
76
+ DestructionMethod2["CRYPTO_SHRED"] = "crypto_shred";
77
+ DestructionMethod2["MEMORY_ZERO"] = "memory_zero";
78
+ DestructionMethod2["SECURE_DELETE"] = "secure_delete";
79
+ DestructionMethod2["TEE_EXIT"] = "tee_exit";
80
+ DestructionMethod2["TTL_EXPIRE"] = "ttl_expire";
81
+ DestructionMethod2["MANUAL"] = "manual";
82
+ return DestructionMethod2;
83
+ })(DestructionMethod || {});
84
+ var ResourceInfo = class {
85
+ constructor(resourceType, resourceId, classification, metadata = {}) {
86
+ this.resourceType = resourceType;
87
+ this.resourceId = resourceId;
88
+ this.classification = classification;
89
+ this.metadata = metadata;
90
+ }
91
+ /**
92
+ * Convert to a plain object for serialization.
93
+ */
94
+ toDict() {
95
+ return {
96
+ type: this.resourceType,
97
+ id: this.resourceId,
98
+ classification: this.classification,
99
+ metadata: this.metadata
100
+ };
101
+ }
102
+ };
103
+ var ChainOfCustody = class {
104
+ constructor(createdAt, createdBy = null) {
105
+ this.createdAt = createdAt;
106
+ this.createdBy = createdBy;
107
+ }
108
+ accessLog = [];
109
+ hashChain = [];
110
+ /**
111
+ * Record an access event and extend the hash chain.
112
+ *
113
+ * @param accessor - Identity of the accessor
114
+ * @param action - Action performed (create, read, destroy, etc.)
115
+ * @param timestamp - Optional timestamp (defaults to now)
116
+ */
117
+ addAccess(accessor, action, timestamp) {
118
+ const ts = timestamp ?? /* @__PURE__ */ new Date();
119
+ const event = {
120
+ timestamp: ts.toISOString(),
121
+ accessor,
122
+ action
123
+ };
124
+ this.accessLog.push(event);
125
+ const eventHash = crypto.createHash("sha256").update(JSON.stringify(event)).digest("hex");
126
+ let chained;
127
+ if (this.hashChain.length > 0) {
128
+ chained = crypto.createHash("sha256").update(this.hashChain[this.hashChain.length - 1] + eventHash).digest("hex");
129
+ } else {
130
+ chained = eventHash;
131
+ }
132
+ this.hashChain.push(chained);
133
+ }
134
+ /**
135
+ * Convert to a plain object for serialization.
136
+ * Returns only the last 5 hashes for brevity.
137
+ */
138
+ toDict() {
139
+ return {
140
+ created_at: this.createdAt.toISOString(),
141
+ created_by: this.createdBy,
142
+ access_count: this.accessLog.length,
143
+ hash_chain: this.hashChain.slice(-5)
144
+ // Last 5 hashes
145
+ };
146
+ }
147
+ };
148
+ var DestructionCertificate = class _DestructionCertificate {
149
+ constructor(certificateId, version, resource, destructionMethod, destructionTimestamp, verifiedBy, chainOfCustody = null, signature = null) {
150
+ this.certificateId = certificateId;
151
+ this.version = version;
152
+ this.resource = resource;
153
+ this.destructionMethod = destructionMethod;
154
+ this.destructionTimestamp = destructionTimestamp;
155
+ this.verifiedBy = verifiedBy;
156
+ this.chainOfCustody = chainOfCustody;
157
+ this.signature = signature;
158
+ }
159
+ /**
160
+ * Factory method to create a new destruction certificate.
161
+ *
162
+ * @param resource - The destroyed resource info
163
+ * @param destructionMethod - How the data was destroyed
164
+ * @param verifiedBy - Authority that verified the destruction
165
+ * @param chainOfCustody - Optional chain of custody
166
+ */
167
+ static create(resource, destructionMethod, verifiedBy = "efsf-local-authority", chainOfCustody = null) {
168
+ return new _DestructionCertificate(
169
+ uuidv4(),
170
+ "1.0",
171
+ resource,
172
+ destructionMethod,
173
+ /* @__PURE__ */ new Date(),
174
+ verifiedBy,
175
+ chainOfCustody
176
+ );
177
+ }
178
+ /**
179
+ * Convert to a plain object for serialization.
180
+ *
181
+ * @param includeSignature - Whether to include the signature field
182
+ */
183
+ toDict(includeSignature = true) {
184
+ const data = {
185
+ version: this.version,
186
+ certificate_id: this.certificateId,
187
+ resource: this.resource.toDict(),
188
+ destruction: {
189
+ method: this.destructionMethod,
190
+ timestamp: this.destructionTimestamp.toISOString(),
191
+ verified_by: this.verifiedBy
192
+ }
193
+ };
194
+ if (this.chainOfCustody) {
195
+ data.chain_of_custody = this.chainOfCustody.toDict();
196
+ }
197
+ if (includeSignature && this.signature) {
198
+ data.signature = this.signature;
199
+ }
200
+ return data;
201
+ }
202
+ /**
203
+ * Convert to JSON string.
204
+ */
205
+ toJSON(indent) {
206
+ return JSON.stringify(this.toDict(), null, indent);
207
+ }
208
+ /**
209
+ * Get canonical byte representation for signing.
210
+ *
211
+ * Uses deterministic JSON serialization (sorted keys, no whitespace)
212
+ * to ensure consistent signatures.
213
+ */
214
+ canonicalBytes() {
215
+ const data = this.toDict(false);
216
+ const sortedJson = JSON.stringify(data, Object.keys(data).sort());
217
+ return Buffer.from(sortedJson, "utf-8");
218
+ }
219
+ /**
220
+ * Compute SHA-256 hash of the certificate.
221
+ */
222
+ computeHash() {
223
+ return crypto.createHash("sha256").update(this.canonicalBytes()).digest("hex");
224
+ }
225
+ };
226
+ var AttestationAuthority = class {
227
+ /**
228
+ * Create a new AttestationAuthority.
229
+ *
230
+ * @param authorityId - Identifier for this authority
231
+ */
232
+ constructor(authorityId = "efsf-local-authority") {
233
+ this.authorityId = authorityId;
234
+ const { privateKey, publicKey } = crypto.generateKeyPairSync("ed25519");
235
+ this.privateKey = privateKey;
236
+ this.publicKey = publicKey;
237
+ }
238
+ privateKey;
239
+ publicKey;
240
+ issuedCertificates = /* @__PURE__ */ new Map();
241
+ /**
242
+ * Get the public key as raw bytes.
243
+ */
244
+ get publicKeyBytes() {
245
+ return this.publicKey.export({ type: "spki", format: "der" });
246
+ }
247
+ /**
248
+ * Get the public key as base64-encoded string.
249
+ */
250
+ get publicKeyB64() {
251
+ return this.publicKeyBytes.toString("base64");
252
+ }
253
+ /**
254
+ * Sign a destruction certificate.
255
+ *
256
+ * @param certificate - The certificate to sign
257
+ * @returns The signed certificate
258
+ */
259
+ signCertificate(certificate) {
260
+ const message = certificate.canonicalBytes();
261
+ const signature = crypto.sign(null, message, this.privateKey);
262
+ certificate.signature = signature.toString("base64");
263
+ certificate.verifiedBy = this.authorityId;
264
+ this.issuedCertificates.set(certificate.certificateId, certificate);
265
+ return certificate;
266
+ }
267
+ /**
268
+ * Verify a certificate's signature.
269
+ *
270
+ * @param certificate - The certificate to verify
271
+ * @returns true if the signature is valid
272
+ * @throws AttestationError if verification fails
273
+ */
274
+ verifyCertificate(certificate) {
275
+ if (!certificate.signature) {
276
+ throw new AttestationError("Certificate has no signature");
277
+ }
278
+ try {
279
+ const message = certificate.canonicalBytes();
280
+ const signature = Buffer.from(certificate.signature, "base64");
281
+ return crypto.verify(null, message, this.publicKey, signature);
282
+ } catch (e) {
283
+ throw new AttestationError(`Signature verification failed: ${e}`);
284
+ }
285
+ }
286
+ /**
287
+ * Issue a new signed destruction certificate.
288
+ *
289
+ * @param resourceType - Type of resource (ephemeral_data, sealed_compute, etc.)
290
+ * @param resourceId - Unique identifier of the resource
291
+ * @param classification - Data classification level
292
+ * @param destructionMethod - How the data was destroyed
293
+ * @param chainOfCustody - Optional chain of custody
294
+ * @param metadata - Optional additional metadata
295
+ * @returns The signed certificate
296
+ */
297
+ issueCertificate(resourceType, resourceId, classification, destructionMethod, chainOfCustody = null, metadata = {}) {
298
+ const resource = new ResourceInfo(resourceType, resourceId, classification, metadata);
299
+ const certificate = DestructionCertificate.create(
300
+ resource,
301
+ destructionMethod,
302
+ this.authorityId,
303
+ chainOfCustody
304
+ );
305
+ return this.signCertificate(certificate);
306
+ }
307
+ /**
308
+ * Retrieve a previously issued certificate by ID.
309
+ *
310
+ * @param certificateId - The certificate ID
311
+ * @returns The certificate or null if not found
312
+ */
313
+ getCertificate(certificateId) {
314
+ return this.issuedCertificates.get(certificateId) ?? null;
315
+ }
316
+ /**
317
+ * List issued certificates with optional filtering.
318
+ *
319
+ * @param resourceId - Optional resource ID to filter by
320
+ * @param since - Optional date to filter certificates issued after
321
+ * @returns List of certificates sorted by destruction timestamp (newest first)
322
+ */
323
+ listCertificates(resourceId, since) {
324
+ let certs = Array.from(this.issuedCertificates.values());
325
+ if (resourceId) {
326
+ certs = certs.filter((c) => c.resource.resourceId === resourceId);
327
+ }
328
+ if (since) {
329
+ certs = certs.filter((c) => c.destructionTimestamp >= since);
330
+ }
331
+ return certs.sort(
332
+ (a, b) => b.destructionTimestamp.getTime() - a.destructionTimestamp.getTime()
333
+ );
334
+ }
335
+ };
336
+
337
+ // src/crypto.ts
338
+ import * as crypto2 from "crypto";
339
+ var ALGORITHM = "aes-256-gcm";
340
+ var KEY_LENGTH = 32;
341
+ var NONCE_LENGTH = 12;
342
+ var TAG_LENGTH = 16;
343
+ var EncryptedPayload = class _EncryptedPayload {
344
+ constructor(ciphertext, nonce, keyId, algorithm = "AES-256-GCM") {
345
+ this.ciphertext = ciphertext;
346
+ this.nonce = nonce;
347
+ this.keyId = keyId;
348
+ this.algorithm = algorithm;
349
+ }
350
+ /**
351
+ * Convert to a plain object for serialization.
352
+ */
353
+ toDict() {
354
+ return {
355
+ ciphertext: this.ciphertext,
356
+ nonce: this.nonce,
357
+ key_id: this.keyId,
358
+ algorithm: this.algorithm
359
+ };
360
+ }
361
+ /**
362
+ * Reconstruct from serialized data.
363
+ */
364
+ static fromDict(data) {
365
+ return new _EncryptedPayload(
366
+ data.ciphertext,
367
+ data.nonce,
368
+ data.key_id,
369
+ data.algorithm ?? "AES-256-GCM"
370
+ );
371
+ }
372
+ };
373
+ var DataEncryptionKey = class _DataEncryptionKey {
374
+ constructor(keyId, _keyMaterial, createdAt, expiresAt) {
375
+ this.keyId = keyId;
376
+ this._keyMaterial = _keyMaterial;
377
+ this.createdAt = createdAt;
378
+ this.expiresAt = expiresAt;
379
+ }
380
+ _destroyed = false;
381
+ _destroyedAt = null;
382
+ /**
383
+ * Generate a new DEK with the specified TTL.
384
+ *
385
+ * @param ttlMs - Time-to-live in milliseconds
386
+ * @param keyId - Optional key identifier (auto-generated if not provided)
387
+ */
388
+ static generate(ttlMs, keyId) {
389
+ const now = /* @__PURE__ */ new Date();
390
+ return new _DataEncryptionKey(
391
+ keyId ?? crypto2.randomBytes(16).toString("hex"),
392
+ crypto2.randomBytes(KEY_LENGTH),
393
+ now,
394
+ new Date(now.getTime() + ttlMs)
395
+ );
396
+ }
397
+ /**
398
+ * Get the key material for cryptographic operations.
399
+ * @throws CryptoError if the key has been destroyed
400
+ */
401
+ get keyMaterial() {
402
+ if (this._destroyed) {
403
+ throw new CryptoError("access", "Key has been destroyed");
404
+ }
405
+ return this._keyMaterial;
406
+ }
407
+ /**
408
+ * Check if the key has been destroyed.
409
+ */
410
+ get destroyed() {
411
+ return this._destroyed;
412
+ }
413
+ /**
414
+ * Get the timestamp when the key was destroyed, if applicable.
415
+ */
416
+ get destroyedAt() {
417
+ return this._destroyedAt;
418
+ }
419
+ /**
420
+ * Check if the key is expired or destroyed.
421
+ */
422
+ get isExpired() {
423
+ return /* @__PURE__ */ new Date() >= this.expiresAt || this._destroyed;
424
+ }
425
+ /**
426
+ * Securely destroy the key material (crypto-shredding).
427
+ *
428
+ * This overwrites the key material with random data then zeros,
429
+ * making any data encrypted with this key permanently unrecoverable.
430
+ *
431
+ * Note: In JavaScript/Node.js, we cannot guarantee memory is fully
432
+ * zeroed due to garbage collection. This is best-effort. For true
433
+ * security guarantees, use hardware security modules (HSM).
434
+ */
435
+ destroy() {
436
+ crypto2.randomFillSync(this._keyMaterial);
437
+ this._keyMaterial.fill(0);
438
+ this._destroyed = true;
439
+ this._destroyedAt = /* @__PURE__ */ new Date();
440
+ }
441
+ };
442
+ var CryptoProvider = class {
443
+ masterKey;
444
+ keys = /* @__PURE__ */ new Map();
445
+ /**
446
+ * Create a new CryptoProvider.
447
+ *
448
+ * @param masterKey - Optional master key for key derivation.
449
+ * If not provided, a random key is generated.
450
+ * In production, this should come from a KMS.
451
+ */
452
+ constructor(masterKey) {
453
+ this.masterKey = masterKey ?? crypto2.randomBytes(KEY_LENGTH);
454
+ }
455
+ /**
456
+ * Generate a new Data Encryption Key with the specified TTL.
457
+ *
458
+ * @param ttlMs - Time-to-live in milliseconds
459
+ * @returns The generated DEK
460
+ */
461
+ generateDEK(ttlMs) {
462
+ const dek = DataEncryptionKey.generate(ttlMs);
463
+ this.keys.set(dek.keyId, dek);
464
+ return dek;
465
+ }
466
+ /**
467
+ * Retrieve a DEK by its identifier.
468
+ *
469
+ * @param keyId - The key identifier
470
+ * @returns The DEK if found and valid, null otherwise
471
+ */
472
+ getDEK(keyId) {
473
+ const dek = this.keys.get(keyId);
474
+ if (!dek || dek.destroyed || dek.isExpired) {
475
+ return null;
476
+ }
477
+ return dek;
478
+ }
479
+ /**
480
+ * Destroy a DEK (crypto-shredding).
481
+ *
482
+ * After destruction, any data encrypted with this key becomes
483
+ * permanently unrecoverable.
484
+ *
485
+ * @param keyId - The key identifier
486
+ * @returns true if the key was found and destroyed, false otherwise
487
+ */
488
+ destroyDEK(keyId) {
489
+ const dek = this.keys.get(keyId);
490
+ if (dek) {
491
+ dek.destroy();
492
+ return true;
493
+ }
494
+ return false;
495
+ }
496
+ /**
497
+ * Encrypt plaintext using AES-256-GCM.
498
+ *
499
+ * @param plaintext - The data to encrypt
500
+ * @param dek - The Data Encryption Key to use
501
+ * @param associatedData - Optional additional authenticated data (AAD)
502
+ * @returns The encrypted payload
503
+ * @throws CryptoError if the key is destroyed or expired
504
+ */
505
+ encrypt(plaintext, dek, associatedData) {
506
+ if (dek.destroyed || dek.isExpired) {
507
+ throw new CryptoError("encrypt", "Key is destroyed or expired");
508
+ }
509
+ try {
510
+ const nonce = crypto2.randomBytes(NONCE_LENGTH);
511
+ const cipher = crypto2.createCipheriv(ALGORITHM, dek.keyMaterial, nonce, {
512
+ authTagLength: TAG_LENGTH
513
+ });
514
+ if (associatedData) {
515
+ cipher.setAAD(associatedData);
516
+ }
517
+ const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
518
+ const authTag = cipher.getAuthTag();
519
+ const combined = Buffer.concat([encrypted, authTag]);
520
+ return new EncryptedPayload(
521
+ combined.toString("base64"),
522
+ nonce.toString("base64"),
523
+ dek.keyId
524
+ );
525
+ } catch (e) {
526
+ throw new CryptoError("encrypt", String(e));
527
+ }
528
+ }
529
+ /**
530
+ * Decrypt an encrypted payload.
531
+ *
532
+ * @param payload - The encrypted payload to decrypt
533
+ * @param associatedData - Optional additional authenticated data (must match encryption)
534
+ * @returns The decrypted plaintext
535
+ * @throws CryptoError if decryption fails or key is unavailable
536
+ */
537
+ decrypt(payload, associatedData) {
538
+ const dek = this.getDEK(payload.keyId);
539
+ if (!dek) {
540
+ throw new CryptoError(
541
+ "decrypt",
542
+ `Key ${payload.keyId} not found or destroyed (data is unrecoverable)`
543
+ );
544
+ }
545
+ try {
546
+ const combined = Buffer.from(payload.ciphertext, "base64");
547
+ const nonce = Buffer.from(payload.nonce, "base64");
548
+ const ciphertext = combined.subarray(0, combined.length - TAG_LENGTH);
549
+ const authTag = combined.subarray(combined.length - TAG_LENGTH);
550
+ const decipher = crypto2.createDecipheriv(ALGORITHM, dek.keyMaterial, nonce, {
551
+ authTagLength: TAG_LENGTH
552
+ });
553
+ decipher.setAuthTag(authTag);
554
+ if (associatedData) {
555
+ decipher.setAAD(associatedData);
556
+ }
557
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
558
+ } catch (e) {
559
+ throw new CryptoError("decrypt", String(e));
560
+ }
561
+ }
562
+ /**
563
+ * Encrypt a JSON-serializable object.
564
+ *
565
+ * @param data - The object to encrypt
566
+ * @param dek - The Data Encryption Key to use
567
+ * @returns The encrypted payload
568
+ */
569
+ encryptJSON(data, dek) {
570
+ const plaintext = Buffer.from(JSON.stringify(data), "utf-8");
571
+ return this.encrypt(plaintext, dek);
572
+ }
573
+ /**
574
+ * Decrypt a payload and parse as JSON.
575
+ *
576
+ * @param payload - The encrypted payload
577
+ * @returns The decrypted object
578
+ */
579
+ decryptJSON(payload) {
580
+ const plaintext = this.decrypt(payload);
581
+ return JSON.parse(plaintext.toString("utf-8"));
582
+ }
583
+ /**
584
+ * Derive a key from the master key using HKDF.
585
+ *
586
+ * @param context - Context bytes for domain separation
587
+ * @param length - Desired key length in bytes (default: 32)
588
+ * @returns The derived key material
589
+ */
590
+ deriveKey(context, length = KEY_LENGTH) {
591
+ return Buffer.from(
592
+ crypto2.hkdfSync("sha256", this.masterKey, Buffer.alloc(0), context, length)
593
+ );
594
+ }
595
+ };
596
+ function constantTimeCompare(a, b) {
597
+ if (a.length !== b.length) {
598
+ return false;
599
+ }
600
+ return crypto2.timingSafeEqual(a, b);
601
+ }
602
+
603
+ // src/record.ts
604
+ import { v4 as uuidv42 } from "uuid";
605
+ var DataClassification = /* @__PURE__ */ ((DataClassification2) => {
606
+ DataClassification2["TRANSIENT"] = "TRANSIENT";
607
+ DataClassification2["SHORT_LIVED"] = "SHORT_LIVED";
608
+ DataClassification2["RETENTION_BOUND"] = "RETENTION_BOUND";
609
+ DataClassification2["PERSISTENT"] = "PERSISTENT";
610
+ return DataClassification2;
611
+ })(DataClassification || {});
612
+ var CLASSIFICATION_DEFAULTS = {
613
+ ["TRANSIENT" /* TRANSIENT */]: 60 * 60 * 1e3,
614
+ // 1 hour
615
+ ["SHORT_LIVED" /* SHORT_LIVED */]: 24 * 60 * 60 * 1e3,
616
+ // 1 day
617
+ ["RETENTION_BOUND" /* RETENTION_BOUND */]: 90 * 24 * 60 * 60 * 1e3,
618
+ // 90 days
619
+ ["PERSISTENT" /* PERSISTENT */]: null
620
+ };
621
+ var CLASSIFICATION_MAX = {
622
+ ["TRANSIENT" /* TRANSIENT */]: 24 * 60 * 60 * 1e3,
623
+ // 24 hours
624
+ ["SHORT_LIVED" /* SHORT_LIVED */]: 7 * 24 * 60 * 60 * 1e3,
625
+ // 7 days
626
+ ["RETENTION_BOUND" /* RETENTION_BOUND */]: 7 * 365 * 24 * 60 * 60 * 1e3,
627
+ // 7 years
628
+ ["PERSISTENT" /* PERSISTENT */]: null
629
+ };
630
+ function getDefaultTTL(classification) {
631
+ return CLASSIFICATION_DEFAULTS[classification];
632
+ }
633
+ function getMaxTTL(classification) {
634
+ return CLASSIFICATION_MAX[classification];
635
+ }
636
+ var EphemeralRecord = class _EphemeralRecord {
637
+ constructor(id, classification, createdAt, expiresAt, ttl, encrypted = true, keyId = null, accessCount = 0, metadata = {}) {
638
+ this.id = id;
639
+ this.classification = classification;
640
+ this.createdAt = createdAt;
641
+ this.expiresAt = expiresAt;
642
+ this.ttl = ttl;
643
+ this.encrypted = encrypted;
644
+ this.keyId = keyId;
645
+ this.accessCount = accessCount;
646
+ this.metadata = metadata;
647
+ }
648
+ /**
649
+ * Factory method to create a new ephemeral record.
650
+ *
651
+ * @param options - Configuration options for the record
652
+ * @returns A new EphemeralRecord instance
653
+ * @throws Error if TTL exceeds the classification's maximum
654
+ */
655
+ static create(options = {}) {
656
+ const classification = options.classification ?? "TRANSIENT" /* TRANSIENT */;
657
+ const now = /* @__PURE__ */ new Date();
658
+ let effectiveTTL = options.ttl ?? getDefaultTTL(classification);
659
+ if (effectiveTTL === null) {
660
+ throw new Error(
661
+ `TTL required for ${classification} classification (no default available)`
662
+ );
663
+ }
664
+ const maxTTL = getMaxTTL(classification);
665
+ if (maxTTL !== null && effectiveTTL > maxTTL) {
666
+ throw new Error(
667
+ `TTL ${effectiveTTL}ms exceeds maximum ${maxTTL}ms for ${classification} classification`
668
+ );
669
+ }
670
+ const expiresAt = new Date(now.getTime() + effectiveTTL);
671
+ return new _EphemeralRecord(
672
+ uuidv42(),
673
+ classification,
674
+ now,
675
+ expiresAt,
676
+ effectiveTTL,
677
+ true,
678
+ uuidv42(),
679
+ // Generate key ID for crypto-shredding
680
+ 0,
681
+ options.metadata ?? {}
682
+ );
683
+ }
684
+ /**
685
+ * Check if the record has expired.
686
+ */
687
+ get isExpired() {
688
+ return /* @__PURE__ */ new Date() >= this.expiresAt;
689
+ }
690
+ /**
691
+ * Get remaining time until expiration in milliseconds.
692
+ */
693
+ get timeRemaining() {
694
+ const remaining = this.expiresAt.getTime() - Date.now();
695
+ return Math.max(remaining, 0);
696
+ }
697
+ /**
698
+ * Convert to a plain object for serialization.
699
+ */
700
+ toDict() {
701
+ return {
702
+ id: this.id,
703
+ classification: this.classification,
704
+ created_at: this.createdAt.toISOString(),
705
+ expires_at: this.expiresAt.toISOString(),
706
+ ttl_seconds: this.ttl / 1e3,
707
+ encrypted: this.encrypted,
708
+ key_id: this.keyId,
709
+ access_count: this.accessCount,
710
+ metadata: this.metadata
711
+ };
712
+ }
713
+ /**
714
+ * Reconstruct an EphemeralRecord from serialized data.
715
+ */
716
+ static fromDict(data) {
717
+ return new _EphemeralRecord(
718
+ data.id,
719
+ data.classification,
720
+ new Date(data.created_at),
721
+ new Date(data.expires_at),
722
+ data.ttl_seconds * 1e3,
723
+ data.encrypted ?? true,
724
+ data.key_id ?? null,
725
+ data.access_count ?? 0,
726
+ data.metadata ?? {}
727
+ );
728
+ }
729
+ };
730
+ function parseTTL(ttlString) {
731
+ const pattern = /^(\d+)\s*(s|sec|seconds?|m|min|minutes?|h|hr|hours?|d|days?)$/i;
732
+ const match = ttlString.trim().match(pattern);
733
+ if (!match) {
734
+ throw new Error(`Cannot parse TTL string: ${ttlString}`);
735
+ }
736
+ const value = parseInt(match[1], 10);
737
+ const unit = match[2].toLowerCase();
738
+ if (unit.startsWith("s")) {
739
+ return value * 1e3;
740
+ } else if (unit.startsWith("m")) {
741
+ return value * 60 * 1e3;
742
+ } else if (unit.startsWith("h")) {
743
+ return value * 60 * 60 * 1e3;
744
+ } else if (unit.startsWith("d")) {
745
+ return value * 24 * 60 * 60 * 1e3;
746
+ }
747
+ throw new Error(`Unknown time unit: ${unit}`);
748
+ }
749
+
750
+ // src/store.ts
751
+ var MemoryBackend = class {
752
+ data = /* @__PURE__ */ new Map();
753
+ async set(key, value, ttlSeconds) {
754
+ this.data.set(key, {
755
+ value,
756
+ expiresAt: Date.now() + ttlSeconds * 1e3
757
+ });
758
+ return true;
759
+ }
760
+ async get(key) {
761
+ const entry = this.data.get(key);
762
+ if (!entry) {
763
+ return null;
764
+ }
765
+ if (Date.now() >= entry.expiresAt) {
766
+ this.data.delete(key);
767
+ return null;
768
+ }
769
+ return entry.value;
770
+ }
771
+ async delete(key) {
772
+ return this.data.delete(key);
773
+ }
774
+ async exists(key) {
775
+ const value = await this.get(key);
776
+ return value !== null;
777
+ }
778
+ async ttl(key) {
779
+ const entry = this.data.get(key);
780
+ if (!entry) {
781
+ return null;
782
+ }
783
+ const remaining = Math.floor((entry.expiresAt - Date.now()) / 1e3);
784
+ return Math.max(0, remaining);
785
+ }
786
+ async close() {
787
+ this.data.clear();
788
+ }
789
+ };
790
+ var RedisBackend = class {
791
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
792
+ client;
793
+ prefix = "efsf:";
794
+ constructor(url, options) {
795
+ try {
796
+ const Redis = __require("ioredis");
797
+ this.client = new Redis(url, options);
798
+ } catch {
799
+ throw new BackendError(
800
+ "redis",
801
+ "ioredis package not installed. Install with: npm install ioredis"
802
+ );
803
+ }
804
+ }
805
+ key(k) {
806
+ return `${this.prefix}${k}`;
807
+ }
808
+ async set(key, value, ttlSeconds) {
809
+ try {
810
+ await this.client.setex(this.key(key), ttlSeconds, value);
811
+ return true;
812
+ } catch (e) {
813
+ throw new BackendError("redis", `Failed to set: ${e}`);
814
+ }
815
+ }
816
+ async get(key) {
817
+ try {
818
+ return await this.client.get(this.key(key));
819
+ } catch (e) {
820
+ throw new BackendError("redis", `Failed to get: ${e}`);
821
+ }
822
+ }
823
+ async delete(key) {
824
+ try {
825
+ const result = await this.client.del(this.key(key));
826
+ return result > 0;
827
+ } catch (e) {
828
+ throw new BackendError("redis", `Failed to delete: ${e}`);
829
+ }
830
+ }
831
+ async exists(key) {
832
+ try {
833
+ const result = await this.client.exists(this.key(key));
834
+ return result > 0;
835
+ } catch (e) {
836
+ throw new BackendError("redis", `Failed to check exists: ${e}`);
837
+ }
838
+ }
839
+ async ttl(key) {
840
+ try {
841
+ const result = await this.client.ttl(this.key(key));
842
+ return result > 0 ? result : null;
843
+ } catch (e) {
844
+ throw new BackendError("redis", `Failed to get TTL: ${e}`);
845
+ }
846
+ }
847
+ async close() {
848
+ await this.client.quit();
849
+ }
850
+ };
851
+ function createBackend(backendUrl) {
852
+ if (backendUrl === "memory://" || backendUrl === "memory") {
853
+ return new MemoryBackend();
854
+ }
855
+ const url = new URL(backendUrl);
856
+ if (url.protocol === "redis:" || url.protocol === "rediss:") {
857
+ return new RedisBackend(backendUrl);
858
+ }
859
+ throw new ValidationError("backend", `Unsupported backend scheme: ${url.protocol}`);
860
+ }
861
+ var EphemeralStore = class {
862
+ backend;
863
+ defaultTTL;
864
+ // ms
865
+ crypto;
866
+ attestationEnabled;
867
+ authority;
868
+ records = /* @__PURE__ */ new Map();
869
+ custody = /* @__PURE__ */ new Map();
870
+ certificates = /* @__PURE__ */ new Map();
871
+ constructor(options = {}) {
872
+ if (typeof options.backend === "string") {
873
+ this.backend = createBackend(options.backend);
874
+ } else if (options.backend) {
875
+ this.backend = options.backend;
876
+ } else {
877
+ this.backend = new MemoryBackend();
878
+ }
879
+ if (typeof options.defaultTTL === "string") {
880
+ this.defaultTTL = parseTTL(options.defaultTTL);
881
+ } else if (typeof options.defaultTTL === "number") {
882
+ this.defaultTTL = options.defaultTTL;
883
+ } else {
884
+ this.defaultTTL = 60 * 60 * 1e3;
885
+ }
886
+ this.crypto = options.cryptoProvider ?? new CryptoProvider();
887
+ this.attestationEnabled = options.attestation ?? true;
888
+ if (this.attestationEnabled) {
889
+ this.authority = options.attestationAuthority ?? new AttestationAuthority();
890
+ } else {
891
+ this.authority = null;
892
+ }
893
+ }
894
+ /**
895
+ * Store data with automatic encryption and TTL.
896
+ *
897
+ * @param data - Data to store (must be JSON-serializable)
898
+ * @param options - Storage options (TTL, classification, metadata)
899
+ * @returns The ephemeral record metadata
900
+ */
901
+ async put(data, options = {}) {
902
+ let classification;
903
+ if (typeof options.classification === "string") {
904
+ classification = options.classification;
905
+ } else {
906
+ classification = options.classification ?? "TRANSIENT" /* TRANSIENT */;
907
+ }
908
+ let effectiveTTL;
909
+ if (options.ttl === void 0) {
910
+ effectiveTTL = this.defaultTTL;
911
+ } else if (typeof options.ttl === "string") {
912
+ effectiveTTL = parseTTL(options.ttl);
913
+ } else {
914
+ effectiveTTL = options.ttl;
915
+ }
916
+ const record = EphemeralRecord.create({
917
+ classification,
918
+ ttl: effectiveTTL,
919
+ metadata: options.metadata
920
+ });
921
+ const dek = this.crypto.generateDEK(effectiveTTL);
922
+ record.keyId = dek.keyId;
923
+ const encrypted = this.crypto.encryptJSON(data, dek);
924
+ const storagePayload = {
925
+ record: record.toDict(),
926
+ encrypted: encrypted.toDict()
927
+ };
928
+ const ttlSeconds = Math.ceil(effectiveTTL / 1e3);
929
+ await this.backend.set(record.id, JSON.stringify(storagePayload), ttlSeconds);
930
+ this.records.set(record.id, record);
931
+ if (this.attestationEnabled) {
932
+ const custody = new ChainOfCustody(record.createdAt, "ephemeral_store");
933
+ custody.addAccess("ephemeral_store", "create");
934
+ this.custody.set(record.id, custody);
935
+ }
936
+ return record;
937
+ }
938
+ /**
939
+ * Retrieve data by record ID.
940
+ *
941
+ * @param recordId - The record identifier
942
+ * @returns The decrypted data
943
+ * @throws RecordNotFoundError if the record doesn't exist
944
+ * @throws RecordExpiredError if the record has expired
945
+ */
946
+ async get(recordId) {
947
+ const raw = await this.backend.get(recordId);
948
+ if (raw === null) {
949
+ if (this.certificates.has(recordId)) {
950
+ throw new RecordExpiredError(recordId);
951
+ }
952
+ throw new RecordNotFoundError(recordId);
953
+ }
954
+ const storagePayload = JSON.parse(raw);
955
+ const record = EphemeralRecord.fromDict(storagePayload.record);
956
+ const encrypted = EncryptedPayload.fromDict(storagePayload.encrypted);
957
+ if (record.isExpired) {
958
+ await this.handleExpiration(recordId);
959
+ throw new RecordExpiredError(recordId, record.expiresAt.toISOString());
960
+ }
961
+ const data = this.crypto.decryptJSON(encrypted);
962
+ const trackedRecord = this.records.get(recordId);
963
+ if (trackedRecord) {
964
+ trackedRecord.accessCount++;
965
+ }
966
+ const custody = this.custody.get(recordId);
967
+ if (custody) {
968
+ custody.addAccess("ephemeral_store", "read");
969
+ }
970
+ return data;
971
+ }
972
+ /**
973
+ * Check if a record exists and is not expired.
974
+ *
975
+ * @param recordId - The record identifier
976
+ */
977
+ async exists(recordId) {
978
+ return this.backend.exists(recordId);
979
+ }
980
+ /**
981
+ * Get remaining TTL for a record in milliseconds.
982
+ *
983
+ * @param recordId - The record identifier
984
+ * @returns Remaining TTL in milliseconds, or null if not found
985
+ */
986
+ async ttl(recordId) {
987
+ const seconds = await this.backend.ttl(recordId);
988
+ if (seconds === null) {
989
+ return null;
990
+ }
991
+ return seconds * 1e3;
992
+ }
993
+ /**
994
+ * Manually destroy a record immediately (crypto-shredding).
995
+ *
996
+ * This destroys the encryption key, making the data permanently
997
+ * unrecoverable, and generates a destruction certificate.
998
+ *
999
+ * @param recordId - The record identifier
1000
+ * @returns The destruction certificate, or null if attestation is disabled
1001
+ */
1002
+ async destroy(recordId) {
1003
+ return this.handleExpiration(recordId, "manual" /* MANUAL */);
1004
+ }
1005
+ /**
1006
+ * Handle record expiration or destruction.
1007
+ */
1008
+ async handleExpiration(recordId, method = "crypto_shred" /* CRYPTO_SHRED */) {
1009
+ const record = this.records.get(recordId);
1010
+ const custody = this.custody.get(recordId);
1011
+ await this.backend.delete(recordId);
1012
+ if (record?.keyId) {
1013
+ this.crypto.destroyDEK(record.keyId);
1014
+ }
1015
+ let certificate = null;
1016
+ if (this.attestationEnabled && this.authority && record) {
1017
+ if (custody) {
1018
+ custody.addAccess("ephemeral_store", "destroy");
1019
+ }
1020
+ certificate = this.authority.issueCertificate(
1021
+ "ephemeral_data",
1022
+ recordId,
1023
+ record.classification,
1024
+ method,
1025
+ custody ?? null,
1026
+ {
1027
+ ttl_seconds: record.ttl / 1e3,
1028
+ access_count: record.accessCount,
1029
+ ...record.metadata
1030
+ }
1031
+ );
1032
+ this.certificates.set(recordId, certificate);
1033
+ }
1034
+ this.records.delete(recordId);
1035
+ this.custody.delete(recordId);
1036
+ return certificate;
1037
+ }
1038
+ /**
1039
+ * Get the destruction certificate for a destroyed record.
1040
+ *
1041
+ * @param recordId - The record identifier
1042
+ * @returns The certificate or null if not found
1043
+ */
1044
+ getDestructionCertificate(recordId) {
1045
+ return this.certificates.get(recordId) ?? null;
1046
+ }
1047
+ /**
1048
+ * List all destruction certificates.
1049
+ *
1050
+ * @param since - Optional date to filter certificates issued after
1051
+ * @returns List of certificates sorted by destruction timestamp (newest first)
1052
+ */
1053
+ listCertificates(since) {
1054
+ let certs = Array.from(this.certificates.values());
1055
+ if (since) {
1056
+ certs = certs.filter((c) => c.destructionTimestamp >= since);
1057
+ }
1058
+ return certs.sort(
1059
+ (a, b) => b.destructionTimestamp.getTime() - a.destructionTimestamp.getTime()
1060
+ );
1061
+ }
1062
+ /**
1063
+ * Get store statistics.
1064
+ */
1065
+ stats() {
1066
+ return {
1067
+ activeRecords: this.records.size,
1068
+ certificatesIssued: this.certificates.size,
1069
+ attestationEnabled: this.attestationEnabled
1070
+ };
1071
+ }
1072
+ /**
1073
+ * Close the store and release resources.
1074
+ */
1075
+ async close() {
1076
+ await this.backend.close();
1077
+ }
1078
+ // ============================================================
1079
+ // Internal accessors for testing
1080
+ // ============================================================
1081
+ /** @internal */
1082
+ get _crypto() {
1083
+ return this.crypto;
1084
+ }
1085
+ /** @internal */
1086
+ get _authority() {
1087
+ return this.authority;
1088
+ }
1089
+ /** @internal */
1090
+ get _records() {
1091
+ return this.records;
1092
+ }
1093
+ };
1094
+
1095
+ // src/sealed.ts
1096
+ import { v4 as uuidv43 } from "uuid";
1097
+ function secureZeroMemory(data) {
1098
+ if (data instanceof Uint8Array || Buffer.isBuffer(data)) {
1099
+ data.fill(0);
1100
+ } else if (Array.isArray(data)) {
1101
+ for (let i = 0; i < data.length; i++) {
1102
+ secureZeroMemory(data[i]);
1103
+ }
1104
+ data.length = 0;
1105
+ } else if (data && typeof data === "object") {
1106
+ for (const key of Object.keys(data)) {
1107
+ secureZeroMemory(data[key]);
1108
+ delete data[key];
1109
+ }
1110
+ }
1111
+ }
1112
+ var SealedContext = class {
1113
+ executionId;
1114
+ startedAt;
1115
+ sensitiveRefs = [];
1116
+ cleanupCallbacks = [];
1117
+ constructor(options = {}) {
1118
+ this.executionId = options.executionId ?? uuidv43();
1119
+ this.startedAt = /* @__PURE__ */ new Date();
1120
+ }
1121
+ /**
1122
+ * Track an object for cleanup on context exit.
1123
+ *
1124
+ * @param obj - Object to track
1125
+ * @returns The same object for chaining
1126
+ */
1127
+ track(obj) {
1128
+ try {
1129
+ this.sensitiveRefs.push(new WeakRef(obj));
1130
+ } catch {
1131
+ }
1132
+ return obj;
1133
+ }
1134
+ /**
1135
+ * Register a cleanup callback to run on context exit.
1136
+ *
1137
+ * @param callback - Function to call during cleanup
1138
+ */
1139
+ onCleanup(callback) {
1140
+ this.cleanupCallbacks.push(callback);
1141
+ }
1142
+ /**
1143
+ * Internal: Run cleanup routines.
1144
+ */
1145
+ _cleanup() {
1146
+ for (const callback of this.cleanupCallbacks) {
1147
+ try {
1148
+ callback();
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ for (const ref of this.sensitiveRefs) {
1153
+ const obj = ref.deref();
1154
+ if (obj !== void 0) {
1155
+ secureZeroMemory(obj);
1156
+ }
1157
+ }
1158
+ this.sensitiveRefs.length = 0;
1159
+ this.cleanupCallbacks.length = 0;
1160
+ const g = global;
1161
+ if (typeof g.gc === "function") {
1162
+ g.gc();
1163
+ }
1164
+ }
1165
+ };
1166
+ var SealedExecution = class _SealedExecution {
1167
+ static defaultAuthority = null;
1168
+ attestation;
1169
+ authority;
1170
+ metadata;
1171
+ context = null;
1172
+ certificate = null;
1173
+ chainOfCustody = null;
1174
+ constructor(options = {}) {
1175
+ this.attestation = options.attestation ?? false;
1176
+ this.metadata = options.metadata ?? {};
1177
+ if (this.attestation) {
1178
+ if (options.authority) {
1179
+ this.authority = options.authority;
1180
+ } else {
1181
+ if (!_SealedExecution.defaultAuthority) {
1182
+ _SealedExecution.defaultAuthority = new AttestationAuthority();
1183
+ }
1184
+ this.authority = _SealedExecution.defaultAuthority;
1185
+ }
1186
+ } else {
1187
+ this.authority = options.authority ?? null;
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Enter the sealed execution context.
1192
+ *
1193
+ * Use with try/finally or the run() method for automatic cleanup.
1194
+ *
1195
+ * @returns The sealed context for tracking objects
1196
+ */
1197
+ enter() {
1198
+ this.context = new SealedContext();
1199
+ if (this.attestation) {
1200
+ this.chainOfCustody = new ChainOfCustody(/* @__PURE__ */ new Date(), "sealed_execution");
1201
+ this.chainOfCustody.addAccess("sealed_execution", "context_enter");
1202
+ }
1203
+ return this.context;
1204
+ }
1205
+ /**
1206
+ * Exit the sealed execution context and destroy all state.
1207
+ *
1208
+ * @param error - Optional error if exiting due to an exception
1209
+ */
1210
+ exit(error) {
1211
+ if (!this.context) {
1212
+ return;
1213
+ }
1214
+ if (this.chainOfCustody) {
1215
+ const action = error ? "context_exit_error" : "context_exit_normal";
1216
+ this.chainOfCustody.addAccess("sealed_execution", action);
1217
+ }
1218
+ this.context._cleanup();
1219
+ if (this.attestation && this.authority) {
1220
+ const resource = new ResourceInfo(
1221
+ "sealed_compute",
1222
+ this.context.executionId,
1223
+ "TRANSIENT",
1224
+ {
1225
+ started_at: this.context.startedAt.toISOString(),
1226
+ duration_ms: Date.now() - this.context.startedAt.getTime(),
1227
+ error: error ? String(error) : null,
1228
+ ...this.metadata
1229
+ }
1230
+ );
1231
+ const cert = DestructionCertificate.create(
1232
+ resource,
1233
+ "memory_zero" /* MEMORY_ZERO */,
1234
+ this.authority.authorityId,
1235
+ this.chainOfCustody
1236
+ );
1237
+ this.certificate = this.authority.signCertificate(cert);
1238
+ }
1239
+ this.context = null;
1240
+ }
1241
+ /**
1242
+ * Run a function within the sealed context.
1243
+ *
1244
+ * Automatically handles enter/exit and cleanup.
1245
+ *
1246
+ * @param fn - Function to execute within the sealed context
1247
+ * @returns The function's return value
1248
+ */
1249
+ async run(fn) {
1250
+ const ctx = this.enter();
1251
+ try {
1252
+ const result = await fn(ctx);
1253
+ this.exit();
1254
+ return result;
1255
+ } catch (error) {
1256
+ this.exit(error);
1257
+ throw error;
1258
+ }
1259
+ }
1260
+ };
1261
+ function sealed(options = {}) {
1262
+ return function(fn) {
1263
+ return async function(...args) {
1264
+ const seal = new SealedExecution({
1265
+ attestation: options.attestation,
1266
+ authority: options.authority,
1267
+ metadata: {
1268
+ function: fn.name || "anonymous",
1269
+ ...options.metadata
1270
+ }
1271
+ });
1272
+ const result = await seal.run(async (ctx) => {
1273
+ for (const arg of args) {
1274
+ if (arg && typeof arg === "object") {
1275
+ ctx.track(arg);
1276
+ }
1277
+ }
1278
+ return fn(...args);
1279
+ });
1280
+ if (options.attestation && seal.certificate && result && typeof result === "object") {
1281
+ result["_destruction_certificate"] = seal.certificate.toDict();
1282
+ }
1283
+ return result;
1284
+ };
1285
+ };
1286
+ }
1287
+
1288
+ // src/index.ts
1289
+ var VERSION = "0.1.0";
1290
+ export {
1291
+ AttestationAuthority,
1292
+ AttestationError,
1293
+ BackendError,
1294
+ ChainOfCustody,
1295
+ CryptoError,
1296
+ CryptoProvider,
1297
+ DataClassification,
1298
+ DataEncryptionKey,
1299
+ DestructionCertificate,
1300
+ DestructionMethod,
1301
+ EFSFError,
1302
+ EncryptedPayload,
1303
+ EphemeralRecord,
1304
+ EphemeralStore,
1305
+ MemoryBackend,
1306
+ RecordExpiredError,
1307
+ RecordNotFoundError,
1308
+ RedisBackend,
1309
+ ResourceInfo,
1310
+ SealedContext,
1311
+ SealedExecution,
1312
+ TTLViolationError,
1313
+ VERSION,
1314
+ ValidationError,
1315
+ constantTimeCompare,
1316
+ createBackend,
1317
+ getDefaultTTL,
1318
+ getMaxTTL,
1319
+ parseTTL,
1320
+ sealed,
1321
+ secureZeroMemory
1322
+ };