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