mask-privacy 4.0.0 → 4.2.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 CHANGED
@@ -1,17 +1,17 @@
1
1
  'use strict';
2
2
 
3
3
  var process2 = require('process');
4
- var path = require('path');
4
+ var path2 = require('path');
5
5
  var os = require('os');
6
- var crypto2 = require('crypto');
6
+ var cryptoNode = require('crypto');
7
+ var fs = require('fs');
7
8
  var buffer = require('buffer');
8
9
  var stream = require('stream');
9
10
  var https = require('https');
10
11
  var http2 = require('http2');
11
- var fs$1 = require('fs/promises');
12
+ var fs2 = require('fs/promises');
12
13
  var http = require('http');
13
14
  var url = require('url');
14
- var fs = require('fs');
15
15
  var child_process = require('child_process');
16
16
  var util = require('util');
17
17
 
@@ -36,11 +36,12 @@ function _interopNamespace(e) {
36
36
  }
37
37
 
38
38
  var process2__namespace = /*#__PURE__*/_interopNamespace(process2);
39
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
39
+ var path2__namespace = /*#__PURE__*/_interopNamespace(path2);
40
40
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
41
- var crypto2__namespace = /*#__PURE__*/_interopNamespace(crypto2);
41
+ var cryptoNode__namespace = /*#__PURE__*/_interopNamespace(cryptoNode);
42
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
42
43
  var http2__default = /*#__PURE__*/_interopDefault(http2);
43
- var fs__default = /*#__PURE__*/_interopDefault(fs$1);
44
+ var fs2__default = /*#__PURE__*/_interopDefault(fs2);
44
45
 
45
46
  var __create = Object.create;
46
47
  var __defProp = Object.defineProperty;
@@ -128,6 +129,11 @@ var init_config = __esm({
128
129
  get MASK_ENCRYPTION_KEY() {
129
130
  return process2__namespace.env.MASK_ENCRYPTION_KEY || null;
130
131
  },
132
+ // JSON map of keyId -> base64 key string for key rotation, e.g. {"v1":"...","v2":"..."}
133
+ // The last entry in the map is treated as the active (encryption) key.
134
+ get MASK_KEYRING() {
135
+ return process2__namespace.env.MASK_KEYRING || null;
136
+ },
131
137
  get MASK_MASTER_KEY() {
132
138
  return process2__namespace.env.MASK_MASTER_KEY || process2__namespace.env.MASK_ENCRYPTION_KEY || "";
133
139
  },
@@ -140,6 +146,9 @@ var init_config = __esm({
140
146
  get MASK_BLIND_INDEX_SALT() {
141
147
  return process2__namespace.env.MASK_BLIND_INDEX_SALT || "mask-blind-index";
142
148
  },
149
+ get MASK_KDF_SALT() {
150
+ return process2__namespace.env.MASK_KDF_SALT || "mask-kdf-v4-argon2id";
151
+ },
143
152
  get VAULT_TOKEN() {
144
153
  return process2__namespace.env.VAULT_TOKEN || null;
145
154
  },
@@ -156,6 +165,9 @@ var init_config = __esm({
156
165
  get MASK_VAULT_CLEANUP_FREQUENCY() {
157
166
  return getEnvFloat("MASK_VAULT_CLEANUP_FREQUENCY", 0.01);
158
167
  },
168
+ get MASK_VAULT_MAX_MEMORY_KEYS() {
169
+ return getEnvInt("MASK_VAULT_MAX_MEMORY_KEYS", 1e5);
170
+ },
159
171
  // --- BACKEND CONNECTIONS ---
160
172
  get MASK_REDIS_URL() {
161
173
  return process2__namespace.env.MASK_REDIS_URL || "redis://localhost:6379/0";
@@ -195,7 +207,7 @@ var init_config = __esm({
195
207
  return process2__namespace.env.MASK_SCANNER_URL || "http://localhost:5001/analyze";
196
208
  },
197
209
  get MASK_MODEL_CACHE_DIR() {
198
- return process2__namespace.env.MASK_MODEL_CACHE_DIR || path__namespace.join(os__namespace.homedir(), ".cache", "mask");
210
+ return process2__namespace.env.MASK_MODEL_CACHE_DIR || path2__namespace.join(os__namespace.homedir(), ".cache", "mask");
199
211
  },
200
212
  // --- TELEMETRY & AUDIT ---
201
213
  get MASK_AUDIT_LOG_STRICT() {
@@ -220,6 +232,16 @@ var init_key_provider = __esm({
220
232
  "src/core/key_provider.ts"() {
221
233
  init_config();
222
234
  BaseKeyProvider = class {
235
+ /**
236
+ * Return a JSON keyring string (e.g. from KMS / Secrets Manager), or null
237
+ * to fall back to the MASK_KEYRING environment variable.
238
+ *
239
+ * Override in KMS-backed providers to source the full keyring from a
240
+ * secure external store, removing the need for MASK_KEYRING in env vars.
241
+ */
242
+ getKeyring() {
243
+ return null;
244
+ }
223
245
  };
224
246
  EnvKeyProvider = class extends BaseKeyProvider {
225
247
  async getEncryptionKey() {
@@ -235,13 +257,17 @@ var init_key_provider = __esm({
235
257
  let key = config.MASK_MASTER_KEY;
236
258
  return key || null;
237
259
  }
260
+ /** Return MASK_KEYRING from environment (default behaviour). */
261
+ async getKeyring() {
262
+ return config.MASK_KEYRING || null;
263
+ }
238
264
  };
239
265
  providerInstance = null;
240
266
  }
241
267
  });
242
268
 
243
269
  // src/core/exceptions.ts
244
- exports.MaskError = void 0; exports.MaskVaultConnectionError = void 0; exports.MaskDecryptionError = void 0; exports.MaskNLPTimeout = void 0; exports.MaskSecurityError = void 0;
270
+ exports.MaskError = void 0; exports.MaskVaultConnectionError = void 0; exports.MaskDecryptionError = void 0; exports.MaskNLPTimeout = void 0; exports.MaskSecurityError = void 0; var TokenCollisionError;
245
271
  var init_exceptions = __esm({
246
272
  "src/core/exceptions.ts"() {
247
273
  exports.MaskError = class extends Error {
@@ -259,6 +285,185 @@ var init_exceptions = __esm({
259
285
  };
260
286
  exports.MaskSecurityError = class extends exports.MaskError {
261
287
  };
288
+ TokenCollisionError = class extends exports.MaskError {
289
+ constructor(token, existingHash, incomingHash) {
290
+ super(
291
+ `Token collision detected for token '${token}'. Existing plaintext hash '${existingHash.slice(0, 8)}\u2026' conflicts with incoming hash '${incomingHash.slice(0, 8)}\u2026'. Increase token entropy or adjust tenant salt configuration.`
292
+ );
293
+ this.token = token;
294
+ this.existingHash = existingHash;
295
+ this.incomingHash = incomingHash;
296
+ }
297
+ };
298
+ }
299
+ });
300
+ var FF1;
301
+ var init_ff1 = __esm({
302
+ "src/core/ff1.ts"() {
303
+ FF1 = class {
304
+ constructor(key, tweak, radix) {
305
+ this.chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
306
+ this.key = key;
307
+ this.tweak = tweak;
308
+ this.radix = radix;
309
+ if (radix > this.chars.length) {
310
+ throw new Error(`Radix ${radix} not supported`);
311
+ }
312
+ }
313
+ _prf(x6) {
314
+ const cipher = cryptoNode__namespace.createCipheriv("aes-256-cbc", this.key, Buffer.alloc(16, 0));
315
+ cipher.setAutoPadding(false);
316
+ return Buffer.concat([cipher.update(x6), cipher.final()]).subarray(-16);
317
+ }
318
+ _ciph(x6) {
319
+ const cipher = cryptoNode__namespace.createCipheriv("aes-256-ecb", this.key, null);
320
+ cipher.setAutoPadding(false);
321
+ return Buffer.concat([cipher.update(x6), cipher.final()]);
322
+ }
323
+ _strToInt(s6) {
324
+ let n6 = 0n;
325
+ const r6 = BigInt(this.radix);
326
+ for (let i6 = 0; i6 < s6.length; i6++) {
327
+ n6 = n6 * r6 + BigInt(this.chars.indexOf(s6[i6]));
328
+ }
329
+ return n6;
330
+ }
331
+ _intToStr(num, length) {
332
+ if (num === 0n) {
333
+ return this.chars[0].repeat(length);
334
+ }
335
+ let digits = [];
336
+ let n6 = num;
337
+ const r6 = BigInt(this.radix);
338
+ while (n6 > 0n) {
339
+ digits.push(this.chars[Number(n6 % r6)]);
340
+ n6 /= r6;
341
+ }
342
+ let s6 = digits.reverse().join("");
343
+ while (s6.length < length) s6 = this.chars[0] + s6;
344
+ return s6;
345
+ }
346
+ _bigintToBuffer(num, bytes) {
347
+ const buf = Buffer.alloc(bytes);
348
+ let n6 = num;
349
+ for (let i6 = bytes - 1; i6 >= 0; i6--) {
350
+ buf[i6] = Number(n6 & 0xFFn);
351
+ n6 >>= 8n;
352
+ }
353
+ return buf;
354
+ }
355
+ encrypt(X) {
356
+ const n6 = X.length;
357
+ const t6 = this.tweak.length;
358
+ if (n6 < 2) return X;
359
+ const u6 = Math.floor(n6 / 2);
360
+ const v7 = n6 - u6;
361
+ let A3 = X.substring(0, u6);
362
+ let B3 = X.substring(u6);
363
+ const b6 = Math.ceil(Math.ceil(v7 * Math.log2(this.radix)) / 8);
364
+ const d6 = 4 * Math.ceil(b6 / 4) + 4;
365
+ const P2 = Buffer.alloc(16);
366
+ P2[0] = 1;
367
+ P2[1] = 2;
368
+ P2[2] = 1;
369
+ P2.writeUIntBE(this.radix, 3, 3);
370
+ P2[6] = 10;
371
+ P2[7] = u6 % 256;
372
+ P2.writeUInt32BE(n6, 8);
373
+ P2.writeUInt32BE(t6, 12);
374
+ for (let i6 = 0; i6 < 10; i6++) {
375
+ const m6 = i6 % 2 === 0 ? u6 : v7;
376
+ const padLen = ((-t6 - b6 - 1) % 16 + 16) % 16;
377
+ const Q2 = Buffer.alloc(t6 + padLen + 1 + b6);
378
+ this.tweak.copy(Q2, 0);
379
+ Q2[t6 + padLen] = i6;
380
+ this._bigintToBuffer(this._strToInt(B3), b6).copy(Q2, t6 + padLen + 1);
381
+ const R2 = this._prf(Buffer.concat([P2, Q2]));
382
+ let S2 = Buffer.from(R2);
383
+ let j6 = 1;
384
+ while (S2.length < d6) {
385
+ const xorBlock = Buffer.alloc(16);
386
+ const jBuf = Buffer.alloc(16);
387
+ jBuf.writeUInt32BE(j6, 12);
388
+ for (let k6 = 0; k6 < 16; k6++) xorBlock[k6] = R2[k6] ^ jBuf[k6];
389
+ S2 = Buffer.concat([S2, this._ciph(xorBlock)]);
390
+ j6++;
391
+ }
392
+ S2 = S2.subarray(0, d6);
393
+ let y3 = 0n;
394
+ for (let k6 = 0; k6 < S2.length; k6++) {
395
+ y3 = (y3 << 8n) + BigInt(S2[k6]);
396
+ }
397
+ const modulo = BigInt(this.radix) ** BigInt(m6);
398
+ const c6 = (this._strToInt(A3) + y3) % modulo;
399
+ const C3 = this._intToStr(c6, m6);
400
+ A3 = B3;
401
+ B3 = C3;
402
+ }
403
+ return A3 + B3;
404
+ }
405
+ decrypt(X) {
406
+ const n6 = X.length;
407
+ const t6 = this.tweak.length;
408
+ if (n6 < 2) return X;
409
+ const u6 = Math.floor(n6 / 2);
410
+ const v7 = n6 - u6;
411
+ let A3 = X.substring(0, u6);
412
+ let B3 = X.substring(u6);
413
+ if (n6 % 2 !== 0) {
414
+ const temp = A3;
415
+ A3 = B3;
416
+ B3 = temp;
417
+ }
418
+ const b6 = Math.ceil(Math.ceil(v7 * Math.log2(this.radix)) / 8);
419
+ const d6 = 4 * Math.ceil(b6 / 4) + 4;
420
+ const P2 = Buffer.alloc(16);
421
+ P2[0] = 1;
422
+ P2[1] = 2;
423
+ P2[2] = 1;
424
+ P2.writeUIntBE(this.radix, 3, 3);
425
+ P2[6] = 10;
426
+ P2[7] = u6 % 256;
427
+ P2.writeUInt32BE(n6, 8);
428
+ P2.writeUInt32BE(t6, 12);
429
+ for (let i6 = 9; i6 >= 0; i6--) {
430
+ const m6 = i6 % 2 === 0 ? u6 : v7;
431
+ const padLen = ((-t6 - b6 - 1) % 16 + 16) % 16;
432
+ const Q2 = Buffer.alloc(t6 + padLen + 1 + b6);
433
+ this.tweak.copy(Q2, 0);
434
+ Q2[t6 + padLen] = i6;
435
+ this._bigintToBuffer(this._strToInt(A3), b6).copy(Q2, t6 + padLen + 1);
436
+ const R2 = this._prf(Buffer.concat([P2, Q2]));
437
+ let S2 = Buffer.from(R2);
438
+ let j6 = 1;
439
+ while (S2.length < d6) {
440
+ const xorBlock = Buffer.alloc(16);
441
+ const jBuf = Buffer.alloc(16);
442
+ jBuf.writeUInt32BE(j6, 12);
443
+ for (let k6 = 0; k6 < 16; k6++) xorBlock[k6] = R2[k6] ^ jBuf[k6];
444
+ S2 = Buffer.concat([S2, this._ciph(xorBlock)]);
445
+ j6++;
446
+ }
447
+ S2 = S2.subarray(0, d6);
448
+ let y3 = 0n;
449
+ for (let k6 = 0; k6 < S2.length; k6++) {
450
+ y3 = (y3 << 8n) + BigInt(S2[k6]);
451
+ }
452
+ const modulo = BigInt(this.radix) ** BigInt(m6);
453
+ let c6 = (this._strToInt(B3) - y3) % modulo;
454
+ if (c6 < 0n) c6 += modulo;
455
+ const C3 = this._intToStr(c6, m6);
456
+ B3 = A3;
457
+ A3 = C3;
458
+ }
459
+ if (n6 % 2 !== 0) {
460
+ const temp = A3;
461
+ A3 = B3;
462
+ B3 = temp;
463
+ }
464
+ return A3 + B3;
465
+ }
466
+ };
262
467
  }
263
468
  });
264
469
 
@@ -485,13 +690,13 @@ function looksLikeToken(value) {
485
690
  if (/^\+[1-9]\d{0,3}-555-\d{7}$/.test(v7)) {
486
691
  return true;
487
692
  }
488
- if (v7.startsWith("000-00-") && v7.length === 11) {
693
+ if (/^\d{3}-\d{2}-\d{4}$/.test(v7)) {
489
694
  return true;
490
695
  }
491
- if (v7.startsWith("4000-0000-0000-") && v7.length === 19) {
696
+ if (/^\d{4}-\d{4}-\d{4}-\d{4}$/.test(v7)) {
492
697
  return true;
493
698
  }
494
- if (v7.startsWith("000000") && v7.length === 9) {
699
+ if (v7.length === 9 && /^\d+$/.test(v7)) {
495
700
  return true;
496
701
  }
497
702
  if (v7.length === 9 && v7.startsWith("000") && /[A-Z]$/.test(v7)) {
@@ -515,11 +720,28 @@ function looksLikeToken(value) {
515
720
  }
516
721
  return false;
517
722
  }
723
+ function isUnambiguouslySafeToken(value) {
724
+ if (typeof value !== "string") return false;
725
+ const v7 = value.trim();
726
+ if (v7.startsWith("tkn-") && v7.includes("@")) {
727
+ const parts = v7.split("@");
728
+ if (parts.length === 2 && parts[0].length >= 12 && parts[1].includes(".")) {
729
+ return true;
730
+ }
731
+ }
732
+ if (/^\+[1-9]\d{0,3}-555-\d{7}$/.test(v7)) return true;
733
+ if (/^000\d{5}[A-Z]$/.test(v7)) return true;
734
+ if (/^[A-Z]{2}00[A-F0-9]{4,16}$/.test(v7)) return true;
735
+ if (/^<(PER|LOC|ORG):[^>]+>$/.test(v7)) return true;
736
+ if (v7.startsWith("[TKN-") && v7.endsWith("]")) return true;
737
+ if (/^[A-Z][a-zA-Z, ]+-[0-9]{3,4}$/.test(v7)) return true;
738
+ return false;
739
+ }
518
740
  var TOKEN_PATTERN;
519
741
  var init_fpe_utils = __esm({
520
742
  "src/core/fpe_utils.ts"() {
521
743
  TOKEN_PATTERN = new RegExp(
522
- "tkn-[a-f0-9]{8,64}@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}|\\+[1-9]\\d{0,3}-555-\\d{7}|000-00-\\d{4}|4000-0000-0000-\\d{4}|000000\\d{3}|000\\d{5}[A-Z]|[A-Z]{2}00[A-F0-9]{4,16}|<(?:PER|LOC|ORG):[^>]+>|\\b[A-Z][a-zA-Z, ]+-[0-9]{3,4}\\b|\\\\[TKN-[a-f0-9]{8,64}\\\\]",
744
+ "tkn-[a-f0-9]{8,64}@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}|\\+[1-9]\\d{0,3}-555-\\d{7}|\\d{3}-\\d{2}-\\d{4}|\\d{4}-\\d{4}-\\d{4}-\\d{4}|\\b\\d{9}\\b|\\b000\\d{5}[A-Z]\\b|[A-Z]{2}00[A-F0-9]{4,16}|<(?:PER|LOC|ORG):[^>]+>|\\b[A-Z][a-zA-Z, ]+-[0-9]{3,4}\\b|\\[TKN-[^\\]]+\\]",
523
745
  // Opaque
524
746
  "g"
525
747
  );
@@ -534,12 +756,10 @@ async function _getMasterKey() {
534
756
  }
535
757
  if (!raw) {
536
758
  if (config.MASK_DEV_MODE) {
537
- raw = crypto2__namespace.randomBytes(32).toString("hex");
759
+ raw = cryptoNode__namespace.randomBytes(32).toString("hex");
538
760
  process.env.MASK_MASTER_KEY = raw;
539
761
  } else {
540
- throw new exports.MaskSecurityError(
541
- "MASK_MASTER_KEY not set. Set it or use MASK_DEV_MODE=true for dev."
542
- );
762
+ throw new exports.MaskSecurityError("MASK_MASTER_KEY not set.");
543
763
  }
544
764
  }
545
765
  _masterKey = Buffer.from(raw, "utf-8");
@@ -549,38 +769,34 @@ async function _getMasterKey() {
549
769
  function resetMasterKey() {
550
770
  _masterKey = null;
551
771
  }
552
- async function _hmacHex(plaintext, n6 = 8) {
772
+ async function _getAesKey() {
553
773
  const masterKey = await _getMasterKey();
554
- const digest = crypto2__namespace.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest("hex");
555
- return digest.slice(0, n6);
774
+ return cryptoNode__namespace.createHmac("sha256", masterKey).update(config.MASK_TENANT_ID, "utf-8").digest();
556
775
  }
557
- async function _hmacInt(plaintext) {
776
+ async function _hmacHex(plaintext, n6 = 8) {
558
777
  const masterKey = await _getMasterKey();
559
- const raw = crypto2__namespace.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest();
560
- let result = 0n;
561
- for (let i6 = 0; i6 < 16; i6++) {
562
- result = result << 8n | BigInt(raw[i6]);
563
- }
564
- return result;
565
- }
566
- async function _hmacDigits(plaintext, n6, offset = 0) {
567
- const salted = offset ? `${plaintext}::${offset}` : plaintext;
568
- const seed = await _hmacInt(salted);
569
- const modulus = 10n ** BigInt(n6);
570
- return (seed % modulus).toString().padStart(n6, "0");
778
+ const digest = cryptoNode__namespace.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest("hex");
779
+ return digest.slice(0, n6);
571
780
  }
572
781
  async function _getBijectiveTweak() {
573
- const masterKey = await _getMasterKey();
574
- let base = config.MASK_TENANT_ID;
575
782
  if (config.MASK_SALT_ROTATION !== "NONE") {
576
- const now = /* @__PURE__ */ new Date();
577
- if (config.MASK_SALT_ROTATION === "MONTHLY") {
578
- base += `-${now.getUTCFullYear()}-${now.getUTCMonth() + 1}`;
579
- } else if (config.MASK_SALT_ROTATION === "YEARLY") {
580
- base += `-${now.getUTCFullYear()}`;
581
- }
783
+ console.warn(
784
+ `[mask] MASK_SALT_ROTATION=${config.MASK_SALT_ROTATION} is deprecated and ignored. Time-based tweaks caused permanent data loss on month/year rollovers. Use MASK_KEYRING for key rotation instead.`
785
+ );
582
786
  }
583
- return crypto2__namespace.createHmac("sha256", masterKey).update(base, "utf-8").digest();
787
+ const masterKey = await _getMasterKey();
788
+ return cryptoNode__namespace.createHmac("sha256", masterKey).update(config.MASK_TENANT_ID, "utf-8").digest();
789
+ }
790
+ async function _encryptBijectiveFF1(text) {
791
+ const canonical = text.toLowerCase().trim();
792
+ const hash = cryptoNode__namespace.createHash("sha256").update(canonical, "utf-8").digest();
793
+ const inputInt = hash.readBigUInt64BE(0);
794
+ const inputStr = inputInt.toString().padStart(20, "0");
795
+ const aesKey = await _getAesKey();
796
+ const tweak = await _getBijectiveTweak();
797
+ const engine = new FF1(aesKey, tweak, 10);
798
+ const cipherStr = engine.encrypt(inputStr);
799
+ return BigInt(cipherStr) % 2n ** 64n;
584
800
  }
585
801
  function _renderBijectivePerson(bits) {
586
802
  const firstIdx = Number(bits & 0x7FFn);
@@ -628,7 +844,10 @@ function _computeLuhnDigit(partialNum) {
628
844
  function _computeEsIdCheck(num) {
629
845
  return "TRWAGMYFPDXBNJZSQVHLCKE"[num % 23];
630
846
  }
631
- async function generateFPEToken(rawText, entityType = "UNKNOWN") {
847
+ function _stripCcSeparators(text) {
848
+ return text.replace(/[\s\-]/g, "");
849
+ }
850
+ async function generateDPToken(rawText, entityType = "UNKNOWN") {
632
851
  const text = rawText.trim();
633
852
  let type = (entityType || "UNKNOWN").toUpperCase();
634
853
  if (type === "UNKNOWN") {
@@ -648,49 +867,73 @@ async function generateFPEToken(rawText, entityType = "UNKNOWN") {
648
867
  if (type === "PHONE_NUMBER" || type === "PHONE_NUM" || type === "PHONE_NUM_INTL") {
649
868
  const m6 = text.match(/^\+([1-9]\d{0,3})/);
650
869
  const cc = m6 ? m6[1] : "1";
651
- return `+${cc}-555-${await _hmacDigits(text, 7)}`;
870
+ const digits = text.replace(/\D/g, "");
871
+ if (digits.length >= 7) {
872
+ const last7 = digits.slice(-7);
873
+ const engine = new FF1(await _getAesKey(), Buffer.from("PHONE"), 10);
874
+ const enc = engine.encrypt(last7);
875
+ return `+${cc}-555-${enc}`;
876
+ }
652
877
  }
653
878
  if (type === "US_SSN") {
654
- return `000-00-${await _hmacDigits(text, 4)}`;
879
+ const digits = text.replace(/-/g, "");
880
+ if (digits.length === 9) {
881
+ const engine = new FF1(await _getAesKey(), Buffer.from("US_SSN"), 10);
882
+ const enc = engine.encrypt(digits);
883
+ return `${enc.slice(0, 3)}-${enc.slice(3, 5)}-${enc.slice(5, 9)}`;
884
+ }
655
885
  }
656
886
  if (type === "CREDIT_CARD" || type === "CREDIT_CARD_NUMBER") {
657
- const base = `400000000000${await _hmacDigits(text, 3)}`;
658
- const checkDig = _computeLuhnDigit(base);
659
- const full = base + checkDig;
660
- return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
887
+ const digits = _stripCcSeparators(text);
888
+ if (digits.length === 16) {
889
+ const bin6 = digits.slice(0, 6);
890
+ const last4 = digits.slice(12, 16);
891
+ const middle6 = digits.slice(6, 12);
892
+ const engine = new FF1(await _getAesKey(), Buffer.from("CREDIT_CARD"), 10);
893
+ const encMiddle = engine.encrypt(middle6);
894
+ const base15 = bin6 + encMiddle + last4.slice(0, 3);
895
+ const checkDig = _computeLuhnDigit(base15);
896
+ const full = bin6 + encMiddle + last4.slice(0, 3) + checkDig;
897
+ return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
898
+ } else {
899
+ const fallbackDigits = digits.padEnd(16, "0").slice(0, 16);
900
+ const engine = new FF1(await _getAesKey(), Buffer.from("CREDIT_CARD"), 10);
901
+ const encMiddle = engine.encrypt(fallbackDigits.slice(6, 12));
902
+ const full = fallbackDigits.slice(0, 6) + encMiddle + fallbackDigits.slice(12);
903
+ return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
904
+ }
661
905
  }
662
906
  if (type === "US_ROUTING_NUMBER" || type === "US_ABA_ROUTING") {
663
- return `000000${await _hmacDigits(text, 3)}`;
907
+ if (text.length === 9 && /^\d+$/.test(text)) {
908
+ const engine = new FF1(await _getAesKey(), Buffer.from("US_ROUTING"), 10);
909
+ return engine.encrypt(text);
910
+ }
664
911
  }
665
912
  if (type === "INTL_BANK_IBAN" || type === "IBAN_CODE") {
666
913
  const countryCode = text.length >= 2 && /[a-zA-Z]{2}/.test(text.slice(0, 2)) ? text.slice(0, 2).toUpperCase() : "US";
667
914
  return `${countryCode}00${(await _hmacHex(text, 8)).toUpperCase()}`;
668
915
  }
669
916
  if (type === "ES_ID" || type === "ES_DNI") {
670
- const digits = `000${await _hmacDigits(text, 5)}`;
671
- return digits + _computeEsIdCheck(parseInt(digits, 10));
917
+ let digits = text.toUpperCase().replace(/[A-Z]/g, "");
918
+ if (digits) {
919
+ digits = digits.padStart(8, "0");
920
+ const engine = new FF1(await _getAesKey(), Buffer.from("ES_ID"), 10);
921
+ const enc = engine.encrypt(digits.slice(-5));
922
+ const tokenDigits = `000${enc}`;
923
+ return tokenDigits + _computeEsIdCheck(parseInt(tokenDigits, 10));
924
+ }
672
925
  }
673
926
  if (type === "PERSON" || type === "PERSON_NAME") {
674
927
  if (config.MASK_BIJECTIVE_MODE) {
675
- const canonical = text.toLowerCase().trim();
676
- const hash = crypto2__namespace.createHash("sha256").update(canonical, "utf-8").digest();
677
- const inputInt = hash.readBigUInt64BE(0);
678
- const masterKey = await _getMasterKey();
679
- const engine = new FF1(masterKey.slice(0, 16), await _getBijectiveTweak());
680
- const cipher = engine.encrypt(inputInt);
681
- return _renderBijectivePerson(cipher);
928
+ const cipherBits = await _encryptBijectiveFF1(text);
929
+ return _renderBijectivePerson(cipherBits);
682
930
  }
683
931
  return `[TKN-PERSON-${await _hmacHex(text)}]`;
684
932
  }
685
933
  if (type === "LOCATION" || type === "PHYS_ADDRESS") {
686
934
  if (config.MASK_BIJECTIVE_MODE) {
687
- const canonical = text.toLowerCase().trim();
688
- const hash = crypto2__namespace.createHash("sha256").update(canonical, "utf-8").digest();
689
- const inputInt = hash.readBigUInt64BE(0);
690
- const masterKey = await _getMasterKey();
691
- const engine = new FF1(masterKey.slice(0, 16), await _getBijectiveTweak());
692
- const cipher = engine.encrypt(inputInt);
693
- return _renderBijectiveLocation(cipher);
935
+ const cipherBits = await _encryptBijectiveFF1(text);
936
+ return _renderBijectiveLocation(cipherBits);
694
937
  }
695
938
  return `[TKN-LOC-${await _hmacHex(text)}]`;
696
939
  }
@@ -699,12 +942,13 @@ async function generateFPEToken(rawText, entityType = "UNKNOWN") {
699
942
  }
700
943
  return `[TKN-${await _hmacHex(text)}]`;
701
944
  }
702
- var _masterKey, _EMAIL_RE, _PHONE_RE, _SSN_RE, _CC_RE, _ROUTING_RE, _ES_ID_RE, _IBAN_RE, FF1;
945
+ var _masterKey, _EMAIL_RE, _PHONE_RE, _SSN_RE, _CC_RE, _ROUTING_RE, _ES_ID_RE, _IBAN_RE; exports.generateFPEToken = void 0;
703
946
  var init_fpe = __esm({
704
947
  "src/core/fpe.ts"() {
705
948
  init_config();
706
949
  init_key_provider();
707
950
  init_exceptions();
951
+ init_ff1();
708
952
  init_synthesisLibrary();
709
953
  init_fpe_utils();
710
954
  _masterKey = null;
@@ -715,49 +959,7 @@ var init_fpe = __esm({
715
959
  _ROUTING_RE = /^\d{9}$/;
716
960
  _ES_ID_RE = /^(?:\d{8}[A-Z]|[XYZ]\d{7}[A-Z])$/;
717
961
  _IBAN_RE = /^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/;
718
- FF1 = class {
719
- /** NIST SP 800-38G FF1 implementation (simplified for 64-bit domains). */
720
- constructor(key, tweak) {
721
- this.key = key;
722
- this.tweak = tweak;
723
- }
724
- encrypt(n6) {
725
- let A3 = n6 >> 32n;
726
- let B3 = n6 & 0xFFFFFFFFn;
727
- const radix = 2n ** 32n;
728
- for (let i6 = 0; i6 < 10; i6++) {
729
- const tweakInfoBuffer = Buffer.alloc(8);
730
- tweakInfoBuffer.writeUInt32BE(i6, 0);
731
- tweakInfoBuffer.writeUInt32BE(Number(B3), 4);
732
- const tweakInfoCombined = Buffer.concat([this.tweak, tweakInfoBuffer]);
733
- const h6 = crypto2__namespace.createHmac("sha256", this.key).update(tweakInfoCombined).digest();
734
- const roundVal = BigInt(h6.readUInt32BE(0));
735
- const Anext = B3;
736
- const Bnext = (A3 + roundVal) % radix;
737
- A3 = Anext;
738
- B3 = Bnext;
739
- }
740
- return A3 << 32n | B3;
741
- }
742
- decrypt(n6) {
743
- let A3 = n6 >> 32n;
744
- let B3 = n6 & 0xFFFFFFFFn;
745
- const radix = 2n ** 32n;
746
- for (let i6 = 9; i6 >= 0; i6--) {
747
- const tweakInfoBuffer = Buffer.alloc(8);
748
- tweakInfoBuffer.writeUInt32BE(i6, 0);
749
- tweakInfoBuffer.writeUInt32BE(Number(A3), 4);
750
- const tweakInfoCombined = Buffer.concat([this.tweak, tweakInfoBuffer]);
751
- const h6 = crypto2__namespace.createHmac("sha256", this.key).update(tweakInfoCombined).digest();
752
- const roundVal = BigInt(h6.readUInt32BE(0));
753
- const Bprev = A3;
754
- const Aprev = (B3 - roundVal + radix) % radix;
755
- A3 = Aprev;
756
- B3 = Bprev;
757
- }
758
- return A3 << 32n | B3;
759
- }
760
- };
962
+ exports.generateFPEToken = generateDPToken;
761
963
  }
762
964
  });
763
965
 
@@ -774,39 +976,39 @@ var require_core = __commonJS({
774
976
  }
775
977
  })(exports2, function() {
776
978
  var CryptoJS = CryptoJS || (function(Math2, undefined2) {
777
- var crypto6;
979
+ var crypto7;
778
980
  if (typeof window !== "undefined" && window.crypto) {
779
- crypto6 = window.crypto;
981
+ crypto7 = window.crypto;
780
982
  }
781
983
  if (typeof self !== "undefined" && self.crypto) {
782
- crypto6 = self.crypto;
984
+ crypto7 = self.crypto;
783
985
  }
784
986
  if (typeof globalThis !== "undefined" && globalThis.crypto) {
785
- crypto6 = globalThis.crypto;
987
+ crypto7 = globalThis.crypto;
786
988
  }
787
- if (!crypto6 && typeof window !== "undefined" && window.msCrypto) {
788
- crypto6 = window.msCrypto;
989
+ if (!crypto7 && typeof window !== "undefined" && window.msCrypto) {
990
+ crypto7 = window.msCrypto;
789
991
  }
790
- if (!crypto6 && typeof global !== "undefined" && global.crypto) {
791
- crypto6 = global.crypto;
992
+ if (!crypto7 && typeof global !== "undefined" && global.crypto) {
993
+ crypto7 = global.crypto;
792
994
  }
793
- if (!crypto6 && typeof __require === "function") {
995
+ if (!crypto7 && typeof __require === "function") {
794
996
  try {
795
- crypto6 = __require("crypto");
997
+ crypto7 = __require("crypto");
796
998
  } catch (err2) {
797
999
  }
798
1000
  }
799
1001
  var cryptoSecureRandomInt = function() {
800
- if (crypto6) {
801
- if (typeof crypto6.getRandomValues === "function") {
1002
+ if (crypto7) {
1003
+ if (typeof crypto7.getRandomValues === "function") {
802
1004
  try {
803
- return crypto6.getRandomValues(new Uint32Array(1))[0];
1005
+ return crypto7.getRandomValues(new Uint32Array(1))[0];
804
1006
  } catch (err2) {
805
1007
  }
806
1008
  }
807
- if (typeof crypto6.randomBytes === "function") {
1009
+ if (typeof crypto7.randomBytes === "function") {
808
1010
  try {
809
- return crypto6.randomBytes(4).readInt32LE();
1011
+ return crypto7.randomBytes(4).readInt32LE();
810
1012
  } catch (err2) {
811
1013
  }
812
1014
  }
@@ -3072,7 +3274,7 @@ var require_fernet = __commonJS({
3072
3274
  var Base64 = require_enc_base64();
3073
3275
  var HmacSHA256 = require_hmac_sha256();
3074
3276
  var URLBase64 = require_urlsafe_base642();
3075
- var crypto6 = __require("crypto");
3277
+ var crypto7 = __require("crypto");
3076
3278
  String.prototype.lpad = function(padString, length) {
3077
3279
  var str = this;
3078
3280
  while (str.length < length) str = padString + str;
@@ -3099,7 +3301,7 @@ var require_fernet = __commonJS({
3099
3301
  return hex;
3100
3302
  };
3101
3303
  var randomHex = function(size) {
3102
- return crypto6.randomBytes(128 / 8).toString("hex");
3304
+ return crypto7.randomBytes(128 / 8).toString("hex");
3103
3305
  };
3104
3306
  var setIV = function setIV2(iv_array) {
3105
3307
  if (iv_array) {
@@ -3182,24 +3384,28 @@ function getCryptoEngine() {
3182
3384
  async function getCryptoEngineAsync() {
3183
3385
  return await CryptoEngine.getInstanceAsync();
3184
3386
  }
3185
- var GCM_IV_BYTES, GCM_AUTH_TAG_BYTES, GCM_ALGORITHM, AES_GCM_PREFIX, _CryptoEngine, CryptoEngine;
3387
+ var AES_KEY_BYTES, GCM_IV_BYTES, GCM_AUTH_TAG_BYTES, GCM_ALGORITHM, AES_V2_PREFIX, AES_GCM_PREFIX, AES_GCM_LEGACY_PREFIX, _CryptoEngine, CryptoEngine;
3186
3388
  var init_crypto = __esm({
3187
3389
  "src/core/crypto.ts"() {
3188
3390
  init_config();
3189
3391
  init_key_provider();
3190
3392
  init_exceptions();
3393
+ AES_KEY_BYTES = 32;
3191
3394
  GCM_IV_BYTES = 12;
3192
3395
  GCM_AUTH_TAG_BYTES = 16;
3193
3396
  GCM_ALGORITHM = "aes-256-gcm";
3194
- AES_GCM_PREFIX = "aes:";
3397
+ AES_V2_PREFIX = "aes:v2:";
3398
+ AES_GCM_PREFIX = "aes:v1:";
3399
+ AES_GCM_LEGACY_PREFIX = "aes:";
3195
3400
  _CryptoEngine = class _CryptoEngine {
3196
3401
  constructor() {
3197
- this._aesKey = null;
3402
+ this._keyring = /* @__PURE__ */ new Map();
3403
+ this._activeKeyId = "default";
3198
3404
  this._indexSecret = null;
3199
3405
  }
3200
- /**
3406
+ /**
3201
3407
  * Return the singleton instance, initialising it if necessary.
3202
- * This is asynchronous because key providers (KMS, etc.) might be async.
3408
+ * Async because Argon2id key derivation is async.
3203
3409
  */
3204
3410
  static async getInstanceAsync() {
3205
3411
  if (this._instance === null) {
@@ -3215,33 +3421,96 @@ var init_crypto = __esm({
3215
3421
  }
3216
3422
  return this._instance;
3217
3423
  }
3218
- /** Clear the singleton instance to force re-initialization (useful for key rotation). */
3424
+ /** Clear the singleton (useful for key rotation / tests). */
3219
3425
  static reset() {
3220
3426
  this._instance = null;
3221
3427
  }
3428
+ async _deriveAesKey(rawKey, keyId) {
3429
+ let argon2;
3430
+ try {
3431
+ argon2 = __require("argon2");
3432
+ } catch (e6) {
3433
+ throw new Error(
3434
+ "The 'argon2' package is required for Mask SDK cryptographic operations. Install with: npm install argon2"
3435
+ );
3436
+ }
3437
+ const kdfSaltStr = config.MASK_KDF_SALT + "-" + config.MASK_TENANT_ID + "-" + keyId;
3438
+ const kdfSaltBytes = cryptoNode__namespace.createHash("sha256").update(kdfSaltStr).digest().subarray(0, 16);
3439
+ return await argon2.hash(rawKey, {
3440
+ type: argon2.argon2id,
3441
+ memoryCost: 19456,
3442
+ timeCost: 2,
3443
+ parallelism: 1,
3444
+ hashLength: AES_KEY_BYTES,
3445
+ salt: kdfSaltBytes,
3446
+ raw: true
3447
+ });
3448
+ }
3222
3449
  async _init() {
3450
+ let argon2;
3451
+ try {
3452
+ argon2 = __require("argon2");
3453
+ } catch (e6) {
3454
+ throw new Error("The 'argon2' package is required. Install with: npm install argon2");
3455
+ }
3223
3456
  const provider = getKeyProvider();
3224
- const keyFromProvider = await provider.getEncryptionKey();
3225
- let key;
3226
- if (!keyFromProvider) {
3227
- if (config.MASK_DEV_MODE) {
3228
- key = crypto2__namespace.randomBytes(32).toString("base64");
3229
- process.env.MASK_ENCRYPTION_KEY = key;
3230
- console.warn(
3231
- "MASK_DEV_MODE is enabled. Using a generated throwaway key. DO NOT USE THIS IN PRODUCTION \u2014 tokens will be lost on restart."
3232
- );
3233
- } else {
3234
- throw new Error(
3235
- "MASK_ENCRYPTION_KEY is not set. Set MASK_ENCRYPTION_KEY to a valid encryption key, or set MASK_DEV_MODE=true to use an ephemeral throwaway key."
3236
- );
3457
+ const rawKeys = /* @__PURE__ */ new Map();
3458
+ let activeKeyId = "default";
3459
+ const keyringJson = await provider.getKeyring();
3460
+ if (keyringJson) {
3461
+ let parsed;
3462
+ try {
3463
+ parsed = JSON.parse(keyringJson);
3464
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3465
+ throw new Error("MASK_KEYRING must be a non-empty JSON object.");
3466
+ }
3467
+ } catch (e6) {
3468
+ throw new Error(`Invalid MASK_KEYRING format: ${e6}`);
3237
3469
  }
3470
+ const entries = Object.entries(parsed);
3471
+ if (entries.length === 0) throw new Error("MASK_KEYRING must contain at least one key.");
3472
+ for (const [kid, k6] of entries) rawKeys.set(kid, k6);
3473
+ activeKeyId = entries[entries.length - 1][0];
3238
3474
  } else {
3239
- key = keyFromProvider;
3240
- }
3241
- this._aesKey = crypto2__namespace.createHash("sha256").update(key).digest();
3242
- const masterKey = await provider.getMasterKey() || key;
3243
- const salt = config.MASK_BLIND_INDEX_SALT;
3244
- this._indexSecret = crypto2__namespace.createHmac("sha256", masterKey).update(salt).digest();
3475
+ const keyFromProvider = await provider.getEncryptionKey();
3476
+ let key;
3477
+ if (!keyFromProvider) {
3478
+ if (config.MASK_DEV_MODE) {
3479
+ key = cryptoNode__namespace.randomBytes(32).toString("base64");
3480
+ process.env.MASK_ENCRYPTION_KEY = key;
3481
+ console.warn(
3482
+ "MASK_DEV_MODE is enabled. Using a generated throwaway key. DO NOT USE THIS IN PRODUCTION \u2014 tokens will be lost on restart."
3483
+ );
3484
+ } else {
3485
+ throw new Error(
3486
+ "MASK_ENCRYPTION_KEY or MASK_KEYRING is not set. Set one of these, or set MASK_DEV_MODE=true for ephemeral use."
3487
+ );
3488
+ }
3489
+ } else {
3490
+ key = keyFromProvider;
3491
+ }
3492
+ rawKeys.set("default", key);
3493
+ activeKeyId = "default";
3494
+ }
3495
+ this._keyring = /* @__PURE__ */ new Map();
3496
+ for (const [kid, rawKey] of rawKeys) {
3497
+ this._keyring.set(kid, await this._deriveAesKey(rawKey, kid));
3498
+ }
3499
+ this._activeKeyId = activeKeyId;
3500
+ const rawKeysArr = Array.from(rawKeys.values());
3501
+ const lastRawKey = rawKeysArr[rawKeysArr.length - 1];
3502
+ const masterKey = await provider.getMasterKey() || lastRawKey;
3503
+ const indexSaltStr = config.MASK_BLIND_INDEX_SALT + "-" + config.MASK_TENANT_ID;
3504
+ const indexSaltBytes = cryptoNode__namespace.createHash("sha256").update(indexSaltStr).digest().subarray(0, 16);
3505
+ this._indexSecret = await argon2.hash(masterKey, {
3506
+ type: argon2.argon2id,
3507
+ memoryCost: 19456,
3508
+ timeCost: 2,
3509
+ parallelism: 1,
3510
+ hashLength: AES_KEY_BYTES,
3511
+ salt: indexSaltBytes,
3512
+ raw: true
3513
+ });
3245
3514
  }
3246
3515
  /** Return the secret used for HMAC-based blind indexing. */
3247
3516
  async getIndexSecret() {
@@ -3250,36 +3519,56 @@ var init_crypto = __esm({
3250
3519
  }
3251
3520
  return this._indexSecret;
3252
3521
  }
3522
+ /** Encrypt plaintext using the active keyring key.
3523
+ * Envelope format: aes:v2:{keyId}:{base64(iv+authTag+ciphertext)}
3524
+ */
3253
3525
  encrypt(plaintext) {
3254
- if (!this._aesKey) {
3255
- throw new Error("CryptoEngine not initialised. AES key missing.");
3256
- }
3257
- const iv = crypto2__namespace.randomBytes(GCM_IV_BYTES);
3258
- const cipher = crypto2__namespace.createCipheriv(GCM_ALGORITHM, this._aesKey, iv);
3259
- const encrypted = Buffer.concat([
3260
- cipher.update(plaintext, "utf8"),
3261
- cipher.final()
3262
- ]);
3526
+ const aesKey = this._keyring.get(this._activeKeyId);
3527
+ if (!aesKey) {
3528
+ throw new Error(`CryptoEngine: active key ID '${this._activeKeyId}' not found in keyring.`);
3529
+ }
3530
+ const iv = cryptoNode__namespace.randomBytes(GCM_IV_BYTES);
3531
+ const cipher = cryptoNode__namespace.createCipheriv(GCM_ALGORITHM, aesKey, iv);
3532
+ const plaintextBuf = Buffer.from(plaintext, "utf8");
3533
+ const encrypted = Buffer.concat([cipher.update(plaintextBuf), cipher.final()]);
3263
3534
  const authTag = cipher.getAuthTag();
3535
+ plaintextBuf.fill(0);
3264
3536
  const combined = Buffer.concat([iv, authTag, encrypted]);
3265
- return AES_GCM_PREFIX + combined.toString("base64");
3537
+ return `${AES_V2_PREFIX}${this._activeKeyId}:${combined.toString("base64")}`;
3266
3538
  }
3539
+ /** Decrypt ciphertext. Supports all historical envelope formats. */
3267
3540
  decrypt(ciphertext) {
3268
- if (!this._aesKey) {
3269
- throw new Error("CryptoEngine not initialised. AES key missing.");
3541
+ if (this._keyring.size === 0) {
3542
+ throw new Error("CryptoEngine not initialised.");
3270
3543
  }
3271
3544
  try {
3545
+ if (ciphertext.startsWith(AES_V2_PREFIX)) {
3546
+ const rest = ciphertext.slice(AES_V2_PREFIX.length);
3547
+ const sep3 = rest.indexOf(":");
3548
+ if (sep3 === -1) throw new Error("Malformed aes:v2 envelope: missing key ID separator.");
3549
+ const keyId = rest.slice(0, sep3);
3550
+ const b64 = rest.slice(sep3 + 1);
3551
+ return this._decryptAesGcm(keyId, b64);
3552
+ }
3272
3553
  if (ciphertext.startsWith(AES_GCM_PREFIX)) {
3273
- return this._decryptAesGcm(ciphertext.slice(AES_GCM_PREFIX.length));
3554
+ return this._decryptAesGcm("default", ciphertext.slice(AES_GCM_PREFIX.length));
3555
+ }
3556
+ if (ciphertext.startsWith(AES_GCM_LEGACY_PREFIX)) {
3557
+ return this._decryptAesGcm("default", ciphertext.slice(AES_GCM_LEGACY_PREFIX.length));
3274
3558
  }
3275
3559
  return this._decryptLegacyFernet(ciphertext);
3276
3560
  } catch (e6) {
3277
- console.error("Failed to decrypt vault payload. Check your MASK_ENCRYPTION_KEY. Inner error:", e6);
3561
+ console.error("Failed to decrypt vault payload. Check your MASK_ENCRYPTION_KEY / MASK_KEYRING. Inner error:", e6);
3278
3562
  throw new exports.MaskDecryptionError("Decryption failed");
3279
3563
  }
3280
3564
  }
3281
- /** Decrypt an AES-256-GCM token (base64 encoded). */
3282
- _decryptAesGcm(b64) {
3565
+ _decryptAesGcm(keyId, b64) {
3566
+ const aesKey = this._keyring.get(keyId);
3567
+ if (!aesKey) {
3568
+ throw new exports.MaskDecryptionError(
3569
+ `No key found for key ID '${keyId}'. Ensure the key is present in MASK_KEYRING.`
3570
+ );
3571
+ }
3283
3572
  const combined = Buffer.from(b64, "base64");
3284
3573
  if (combined.length < GCM_IV_BYTES + GCM_AUTH_TAG_BYTES) {
3285
3574
  throw new Error("Ciphertext too short for AES-GCM");
@@ -3287,22 +3576,11 @@ var init_crypto = __esm({
3287
3576
  const iv = combined.subarray(0, GCM_IV_BYTES);
3288
3577
  const authTag = combined.subarray(GCM_IV_BYTES, GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3289
3578
  const encrypted = combined.subarray(GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3290
- const decipher = crypto2__namespace.createDecipheriv(GCM_ALGORITHM, this._aesKey, iv);
3579
+ const decipher = cryptoNode__namespace.createDecipheriv(GCM_ALGORITHM, aesKey, iv);
3291
3580
  decipher.setAuthTag(authTag);
3292
- const decrypted = Buffer.concat([
3293
- decipher.update(encrypted),
3294
- decipher.final()
3295
- ]);
3581
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
3296
3582
  return decrypted.toString("utf8");
3297
3583
  }
3298
- /**
3299
- * Attempt to decrypt a legacy Fernet-format token.
3300
- *
3301
- * Fernet format: Version (1) || Timestamp (8) || IV (16) || Ciphertext (var) || HMAC (32)
3302
- * All base64url-encoded.
3303
- *
3304
- * We try to use the `fernet` npm package if available, otherwise throw.
3305
- */
3306
3584
  _decryptLegacyFernet(ciphertext) {
3307
3585
  let fernet;
3308
3586
  try {
@@ -3330,8 +3608,6 @@ var init_crypto = __esm({
3330
3608
  CryptoEngine = _CryptoEngine;
3331
3609
  }
3332
3610
  });
3333
-
3334
- // src/telemetry/audit_logger.ts
3335
3611
  function _getLogLevel() {
3336
3612
  const env3 = config.MASK_LOG_LEVEL;
3337
3613
  return env3 in LOG_LEVELS ? env3 : "info";
@@ -3365,16 +3641,15 @@ function getLogger(name) {
3365
3641
  error: (...args) => _log("error", ...args)
3366
3642
  };
3367
3643
  }
3368
- function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null) {
3644
+ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null, instanceId = "") {
3369
3645
  const event = {
3370
3646
  ts: Date.now() / 1e3,
3371
3647
  action,
3372
- // "encode" | "decode" | "expired" | "error"
3373
3648
  token,
3374
3649
  data_type: dataType,
3375
- // "email" | "phone" | "ssn" | "opaque"
3376
3650
  agent,
3377
- tool
3651
+ tool,
3652
+ instance_id: instanceId
3378
3653
  };
3379
3654
  if (extra) {
3380
3655
  Object.assign(event, _deepMask(extra));
@@ -3384,7 +3659,7 @@ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null
3384
3659
  function _deepMask(obj) {
3385
3660
  if (obj === null || obj === void 0) return obj;
3386
3661
  if (typeof obj === "string") {
3387
- return looksLikeToken(obj) ? obj : "[REDACTED]";
3662
+ return isUnambiguouslySafeToken(obj) ? obj : "[REDACTED]";
3388
3663
  }
3389
3664
  if (typeof obj !== "object") return obj;
3390
3665
  if (Array.isArray(obj)) {
@@ -3418,6 +3693,10 @@ var init_audit_logger = __esm({
3418
3693
  this._shutdownRegistered = false;
3419
3694
  this._maxBufferSize = config.MASK_AUDIT_MAX_BUFFER_SIZE;
3420
3695
  this._strictMode = config.MASK_AUDIT_LOG_STRICT;
3696
+ const rawKey = process.env.MASK_MASTER_KEY || process.env.MASK_ENCRYPTION_KEY || "";
3697
+ this._signingKey = cryptoNode__namespace.createHash("sha256").update(rawKey).digest();
3698
+ this._instanceId = cryptoNode__namespace.randomUUID();
3699
+ this._prevSig = cryptoNode__namespace.createHmac("sha256", this._signingKey).update(this._instanceId, "utf-8").digest("hex");
3421
3700
  }
3422
3701
  static getInstance() {
3423
3702
  if (this._instance === null) {
@@ -3425,16 +3704,46 @@ var init_audit_logger = __esm({
3425
3704
  }
3426
3705
  return this._instance;
3427
3706
  }
3707
+ _getOverflowPath() {
3708
+ const d6 = process.env.MASK_SECURE_AUDIT_LOG_DIR || __require("os").tmpdir();
3709
+ return path2__namespace.join(d6, `mask_audit_overflow_${this._instanceId}.ndjson`);
3710
+ }
3711
+ _writeOverflow(event) {
3712
+ try {
3713
+ fs__namespace.appendFileSync(this._getOverflowPath(), JSON.stringify(event) + "\n", "utf-8");
3714
+ } catch {
3715
+ }
3716
+ }
3717
+ _consumeOverflow(events) {
3718
+ const overflowPath = this._getOverflowPath();
3719
+ if (!fs__namespace.existsSync(overflowPath)) return;
3720
+ const processingPath = overflowPath + ".processing";
3721
+ try {
3722
+ fs__namespace.renameSync(overflowPath, processingPath);
3723
+ } catch {
3724
+ return;
3725
+ }
3726
+ try {
3727
+ const content = fs__namespace.readFileSync(processingPath, "utf-8");
3728
+ for (const line of content.split("\n")) {
3729
+ if (line.trim()) events.push(JSON.parse(line));
3730
+ }
3731
+ fs__namespace.unlinkSync(processingPath);
3732
+ } catch (e6) {
3733
+ _logger.error(`Failed to consume overflow: ${e6}`);
3734
+ }
3735
+ }
3428
3736
  log(action, token, dataType = "opaque", agent = "", tool = "", extra = {}) {
3429
- const event = _makeEvent(action, token, dataType, agent, tool, extra);
3737
+ const event = _makeEvent(action, token, dataType, agent, tool, extra, this._instanceId);
3430
3738
  if (this._buffer.length >= this._maxBufferSize) {
3431
3739
  if (!this._bufferFullWarned) {
3432
3740
  _logger.warn(
3433
- `AuditLogger buffer full (max=${this._maxBufferSize}). Performing emergency sync-flush to prevent data loss.`
3741
+ `AuditLogger buffer full (max=${this._maxBufferSize}). Spooling to disk overflow to prevent OOM.`
3434
3742
  );
3435
3743
  this._bufferFullWarned = true;
3436
3744
  }
3437
- this._flushSync();
3745
+ this._writeOverflow(event);
3746
+ return;
3438
3747
  }
3439
3748
  this._buffer.push(event);
3440
3749
  }
@@ -3464,27 +3773,84 @@ var init_audit_logger = __esm({
3464
3773
  await this._flush();
3465
3774
  }
3466
3775
  async _flush() {
3467
- if (this._isFlushing || this._buffer.length === 0) return;
3776
+ if (this._isFlushing) return;
3468
3777
  this._isFlushing = true;
3469
3778
  try {
3470
3779
  const events = [...this._buffer];
3471
3780
  this._buffer = [];
3472
3781
  this._bufferFullWarned = false;
3782
+ this._consumeOverflow(events);
3783
+ if (events.length === 0) return;
3784
+ const secureLogDir = process.env.MASK_SECURE_AUDIT_LOG_DIR || "";
3785
+ let secureStream = null;
3786
+ if (secureLogDir) {
3787
+ fs__namespace.mkdirSync(secureLogDir, { recursive: true });
3788
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3789
+ const filePath = path2__namespace.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3790
+ try {
3791
+ secureStream = fs__namespace.createWriteStream(filePath, { flags: "a" });
3792
+ } catch {
3793
+ }
3794
+ }
3473
3795
  for (const evt of events) {
3474
- const json = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3475
- console.info(json);
3796
+ const body = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3797
+ const sigInput = Buffer.from(this._prevSig + body, "utf-8");
3798
+ const sig = cryptoNode__namespace.createHmac("sha256", this._signingKey).update(sigInput).digest("hex");
3799
+ const signedLine = JSON.stringify({
3800
+ ...evt,
3801
+ prev_sig: this._prevSig,
3802
+ sig
3803
+ }, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3804
+ this._prevSig = sig;
3805
+ console.info(signedLine);
3806
+ if (secureStream) {
3807
+ secureStream.write(signedLine + "\n");
3808
+ }
3809
+ }
3810
+ if (secureStream) {
3811
+ secureStream.end();
3476
3812
  }
3477
3813
  } finally {
3478
3814
  this._isFlushing = false;
3479
3815
  }
3480
3816
  }
3481
- /** Synchronous flush for use in signal handlers where async is unreliable. */
3817
+ /** Synchronous flush for use in signal handlers where async is unreliable.
3818
+ *
3819
+ * Computes HMAC signatures to maintain chain integrity and writes to the
3820
+ * secure ndjson audit file (MASK_SECURE_AUDIT_LOG_DIR) if configured,
3821
+ * ensuring SOC 2 tamper-evidence guarantees hold through process shutdown.
3822
+ */
3482
3823
  _flushSync() {
3483
3824
  if (this._buffer.length === 0) return;
3484
3825
  const events = [...this._buffer];
3485
3826
  this._buffer = [];
3827
+ const secureLogDir = process.env.MASK_SECURE_AUDIT_LOG_DIR || "";
3828
+ let secureFilePath = null;
3829
+ if (secureLogDir) {
3830
+ try {
3831
+ fs__namespace.mkdirSync(secureLogDir, { recursive: true });
3832
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3833
+ secureFilePath = path2__namespace.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3834
+ } catch {
3835
+ }
3836
+ }
3486
3837
  for (const evt of events) {
3487
- process.stdout.write(JSON.stringify(evt) + "\n");
3838
+ const body = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3839
+ const sigInput = Buffer.from(this._prevSig + body, "utf-8");
3840
+ const sig = cryptoNode__namespace.createHmac("sha256", this._signingKey).update(sigInput).digest("hex");
3841
+ const signedLine = JSON.stringify({
3842
+ ...evt,
3843
+ prev_sig: this._prevSig,
3844
+ sig
3845
+ }, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3846
+ this._prevSig = sig;
3847
+ process.stdout.write(signedLine + "\n");
3848
+ if (secureFilePath) {
3849
+ try {
3850
+ fs__namespace.appendFileSync(secureFilePath, signedLine + "\n", { encoding: "utf-8" });
3851
+ } catch {
3852
+ }
3853
+ }
3488
3854
  }
3489
3855
  }
3490
3856
  };
@@ -3536,7 +3902,7 @@ var init_search = __esm({
3536
3902
  static async getBucketIndex(bucketVal) {
3537
3903
  const engine = await getCryptoEngine();
3538
3904
  const secret = await engine.getIndexSecret();
3539
- return crypto2__namespace.createHmac("sha256", secret).update(bucketVal).digest("hex");
3905
+ return cryptoNode__namespace.createHmac("sha256", secret).update(bucketVal).digest("hex");
3540
3906
  }
3541
3907
  };
3542
3908
  }
@@ -15682,9 +16048,9 @@ var init_createPaginator = __esm({
15682
16048
  command = withCommand(command) ?? command;
15683
16049
  return await client.send(command, ...args);
15684
16050
  };
15685
- get = (fromObject, path3) => {
16051
+ get = (fromObject, path4) => {
15686
16052
  let cursor = fromObject;
15687
- const pathComponents = path3.split(".");
16053
+ const pathComponents = path4.split(".");
15688
16054
  for (const step of pathComponents) {
15689
16055
  if (!cursor || typeof cursor !== "object") {
15690
16056
  return void 0;
@@ -16295,12 +16661,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
16295
16661
  const password = request2.password ?? "";
16296
16662
  auth = `${username}:${password}`;
16297
16663
  }
16298
- let path3 = request2.path;
16664
+ let path4 = request2.path;
16299
16665
  if (queryString) {
16300
- path3 += `?${queryString}`;
16666
+ path4 += `?${queryString}`;
16301
16667
  }
16302
16668
  if (request2.fragment) {
16303
- path3 += `#${request2.fragment}`;
16669
+ path4 += `#${request2.fragment}`;
16304
16670
  }
16305
16671
  let hostname = request2.hostname ?? "";
16306
16672
  if (hostname[0] === "[" && hostname.endsWith("]")) {
@@ -16312,7 +16678,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
16312
16678
  headers: request2.headers,
16313
16679
  host: hostname,
16314
16680
  method: request2.method,
16315
- path: path3,
16681
+ path: path4,
16316
16682
  port: request2.port,
16317
16683
  agent,
16318
16684
  auth
@@ -16619,16 +16985,16 @@ var init_node_http2_handler = __esm({
16619
16985
  reject(err2);
16620
16986
  };
16621
16987
  const queryString = buildQueryString(query || {});
16622
- let path3 = request2.path;
16988
+ let path4 = request2.path;
16623
16989
  if (queryString) {
16624
- path3 += `?${queryString}`;
16990
+ path4 += `?${queryString}`;
16625
16991
  }
16626
16992
  if (request2.fragment) {
16627
- path3 += `#${request2.fragment}`;
16993
+ path4 += `#${request2.fragment}`;
16628
16994
  }
16629
16995
  const req = session.request({
16630
16996
  ...request2.headers,
16631
- [http2.constants.HTTP2_HEADER_PATH]: path3,
16997
+ [http2.constants.HTTP2_HEADER_PATH]: path4,
16632
16998
  [http2.constants.HTTP2_HEADER_METHOD]: method
16633
16999
  });
16634
17000
  session.ref();
@@ -18077,10 +18443,10 @@ var init_date_utils = __esm({
18077
18443
  };
18078
18444
  }
18079
18445
  });
18080
- var randomUUID;
18446
+ var randomUUID2;
18081
18447
  var init_randomUUID = __esm({
18082
18448
  "node_modules/@smithy/uuid/dist-es/randomUUID.js"() {
18083
- randomUUID = crypto2__namespace.default.randomUUID.bind(crypto2__namespace.default);
18449
+ randomUUID2 = cryptoNode__namespace.default.randomUUID.bind(cryptoNode__namespace.default);
18084
18450
  }
18085
18451
  });
18086
18452
 
@@ -18091,8 +18457,8 @@ var init_v4 = __esm({
18091
18457
  init_randomUUID();
18092
18458
  decimalToHex = Array.from({ length: 256 }, (_, i6) => i6.toString(16).padStart(2, "0"));
18093
18459
  v4 = () => {
18094
- if (randomUUID) {
18095
- return randomUUID();
18460
+ if (randomUUID2) {
18461
+ return randomUUID2();
18096
18462
  }
18097
18463
  const rnds = new Uint8Array(16);
18098
18464
  crypto.getRandomValues(rnds);
@@ -18830,11 +19196,11 @@ var init_HttpBindingProtocol = __esm({
18830
19196
  const opTraits = translateTraits(operationSchema.traits);
18831
19197
  if (opTraits.http) {
18832
19198
  request2.method = opTraits.http[0];
18833
- const [path3, search] = opTraits.http[1].split("?");
19199
+ const [path4, search] = opTraits.http[1].split("?");
18834
19200
  if (request2.path == "/") {
18835
- request2.path = path3;
19201
+ request2.path = path4;
18836
19202
  } else {
18837
- request2.path += path3;
19203
+ request2.path += path4;
18838
19204
  }
18839
19205
  const traitSearchParams = new URLSearchParams(search ?? "");
18840
19206
  Object.assign(query, Object.fromEntries(traitSearchParams));
@@ -19867,18 +20233,18 @@ var getAttrPathList;
19867
20233
  var init_getAttrPathList = __esm({
19868
20234
  "node_modules/@smithy/util-endpoints/dist-es/lib/getAttrPathList.js"() {
19869
20235
  init_types2();
19870
- getAttrPathList = (path3) => {
19871
- const parts = path3.split(".");
20236
+ getAttrPathList = (path4) => {
20237
+ const parts = path4.split(".");
19872
20238
  const pathList = [];
19873
20239
  for (const part of parts) {
19874
20240
  const squareBracketIndex = part.indexOf("[");
19875
20241
  if (squareBracketIndex !== -1) {
19876
20242
  if (part.indexOf("]") !== part.length - 1) {
19877
- throw new EndpointError(`Path: '${path3}' does not end with ']'`);
20243
+ throw new EndpointError(`Path: '${path4}' does not end with ']'`);
19878
20244
  }
19879
20245
  const arrayIndex = part.slice(squareBracketIndex + 1, -1);
19880
20246
  if (Number.isNaN(parseInt(arrayIndex))) {
19881
- throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path3}'`);
20247
+ throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path4}'`);
19882
20248
  }
19883
20249
  if (squareBracketIndex !== 0) {
19884
20250
  pathList.push(part.slice(0, squareBracketIndex));
@@ -19899,9 +20265,9 @@ var init_getAttr = __esm({
19899
20265
  "node_modules/@smithy/util-endpoints/dist-es/lib/getAttr.js"() {
19900
20266
  init_types2();
19901
20267
  init_getAttrPathList();
19902
- getAttr = (value, path3) => getAttrPathList(path3).reduce((acc, index) => {
20268
+ getAttr = (value, path4) => getAttrPathList(path4).reduce((acc, index) => {
19903
20269
  if (typeof acc !== "object") {
19904
- throw new EndpointError(`Index '${index}' in '${path3}' not found in '${JSON.stringify(value)}'`);
20270
+ throw new EndpointError(`Index '${index}' in '${path4}' not found in '${JSON.stringify(value)}'`);
19905
20271
  } else if (Array.isArray(acc)) {
19906
20272
  return acc[parseInt(index)];
19907
20273
  }
@@ -19943,8 +20309,8 @@ var init_parseURL = __esm({
19943
20309
  return value;
19944
20310
  }
19945
20311
  if (typeof value === "object" && "hostname" in value) {
19946
- const { hostname: hostname2, port, protocol: protocol2 = "", path: path3 = "", query = {} } = value;
19947
- const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path3}`);
20312
+ const { hostname: hostname2, port, protocol: protocol2 = "", path: path4 = "", query = {} } = value;
20313
+ const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path4}`);
19948
20314
  url.search = Object.entries(query).map(([k6, v7]) => `${k6}=${v7}`).join("&");
19949
20315
  return url;
19950
20316
  }
@@ -21699,10 +22065,10 @@ ${longDate}
21699
22065
  ${credentialScope}
21700
22066
  ${toHex(hashedRequest)}`;
21701
22067
  }
21702
- getCanonicalPath({ path: path3 }) {
22068
+ getCanonicalPath({ path: path4 }) {
21703
22069
  if (this.uriEscapePath) {
21704
22070
  const normalizedPathSegments = [];
21705
- for (const pathSegment of path3.split("/")) {
22071
+ for (const pathSegment of path4.split("/")) {
21706
22072
  if (pathSegment?.length === 0)
21707
22073
  continue;
21708
22074
  if (pathSegment === ".")
@@ -21713,11 +22079,11 @@ ${toHex(hashedRequest)}`;
21713
22079
  normalizedPathSegments.push(pathSegment);
21714
22080
  }
21715
22081
  }
21716
- const normalizedPath = `${path3?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path3?.endsWith("/") ? "/" : ""}`;
22082
+ const normalizedPath = `${path4?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path4?.endsWith("/") ? "/" : ""}`;
21717
22083
  const doubleEncoded = escapeUri(normalizedPath);
21718
22084
  return doubleEncoded.replace(/%2F/g, "/");
21719
22085
  }
21720
- return path3;
22086
+ return path4;
21721
22087
  }
21722
22088
  validateResolvedCredentials(credentials) {
21723
22089
  if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") {
@@ -27712,8 +28078,8 @@ var init_createConfigValueProvider = __esm({
27712
28078
  return endpoint.url.href;
27713
28079
  }
27714
28080
  if ("hostname" in endpoint) {
27715
- const { protocol, hostname, port, path: path3 } = endpoint;
27716
- return `${protocol}//${hostname}${port ? ":" + port : ""}${path3}`;
28081
+ const { protocol, hostname, port, path: path4 } = endpoint;
28082
+ return `${protocol}//${hostname}${port ? ":" + port : ""}${path4}`;
27717
28083
  }
27718
28084
  }
27719
28085
  return endpoint;
@@ -27771,7 +28137,7 @@ var init_getHomeDir = __esm({
27771
28137
  return "DEFAULT";
27772
28138
  };
27773
28139
  getHomeDir = () => {
27774
- const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${path.sep}` } = process.env;
28140
+ const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${path2.sep}` } = process.env;
27775
28141
  if (HOME)
27776
28142
  return HOME;
27777
28143
  if (USERPROFILE)
@@ -27800,9 +28166,9 @@ var init_getSSOTokenFilepath = __esm({
27800
28166
  "node_modules/@smithy/shared-ini-file-loader/dist-es/getSSOTokenFilepath.js"() {
27801
28167
  init_getHomeDir();
27802
28168
  getSSOTokenFilepath = (id) => {
27803
- const hasher = crypto2.createHash("sha1");
28169
+ const hasher = cryptoNode.createHash("sha1");
27804
28170
  const cacheName = hasher.update(id).digest("hex");
27805
- return path.join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
28171
+ return path2.join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
27806
28172
  };
27807
28173
  }
27808
28174
  });
@@ -27816,7 +28182,7 @@ var init_getSSOTokenFromFile = __esm({
27816
28182
  return tokenIntercept[id];
27817
28183
  }
27818
28184
  const ssoTokenFilepath = getSSOTokenFilepath(id);
27819
- const ssoTokenText = await fs$1.readFile(ssoTokenFilepath, "utf8");
28185
+ const ssoTokenText = await fs2.readFile(ssoTokenFilepath, "utf8");
27820
28186
  return JSON.parse(ssoTokenText);
27821
28187
  };
27822
28188
  }
@@ -27857,7 +28223,7 @@ var init_getConfigFilepath = __esm({
27857
28223
  "node_modules/@smithy/shared-ini-file-loader/dist-es/getConfigFilepath.js"() {
27858
28224
  init_getHomeDir();
27859
28225
  ENV_CONFIG_PATH = "AWS_CONFIG_FILE";
27860
- getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path.join(getHomeDir(), ".aws", "config");
28226
+ getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path2.join(getHomeDir(), ".aws", "config");
27861
28227
  }
27862
28228
  });
27863
28229
  var ENV_CREDENTIALS_PATH, getCredentialsFilepath;
@@ -27865,7 +28231,7 @@ var init_getCredentialsFilepath = __esm({
27865
28231
  "node_modules/@smithy/shared-ini-file-loader/dist-es/getCredentialsFilepath.js"() {
27866
28232
  init_getHomeDir();
27867
28233
  ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE";
27868
- getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path.join(getHomeDir(), ".aws", "credentials");
28234
+ getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path2.join(getHomeDir(), ".aws", "credentials");
27869
28235
  }
27870
28236
  });
27871
28237
 
@@ -27929,14 +28295,14 @@ var init_readFile = __esm({
27929
28295
  "node_modules/@smithy/shared-ini-file-loader/dist-es/readFile.js"() {
27930
28296
  filePromises = {};
27931
28297
  fileIntercept = {};
27932
- readFile2 = (path3, options) => {
27933
- if (fileIntercept[path3] !== void 0) {
27934
- return fileIntercept[path3];
28298
+ readFile2 = (path4, options) => {
28299
+ if (fileIntercept[path4] !== void 0) {
28300
+ return fileIntercept[path4];
27935
28301
  }
27936
- if (!filePromises[path3] || options?.ignoreCache) {
27937
- filePromises[path3] = fs$1.readFile(path3, "utf8");
28302
+ if (!filePromises[path4] || options?.ignoreCache) {
28303
+ filePromises[path4] = fs2.readFile(path4, "utf8");
27938
28304
  }
27939
- return filePromises[path3];
28305
+ return filePromises[path4];
27940
28306
  };
27941
28307
  }
27942
28308
  });
@@ -27957,11 +28323,11 @@ var init_loadSharedConfigFiles = __esm({
27957
28323
  const relativeHomeDirPrefix = "~/";
27958
28324
  let resolvedFilepath = filepath;
27959
28325
  if (filepath.startsWith(relativeHomeDirPrefix)) {
27960
- resolvedFilepath = path.join(homeDir, filepath.slice(2));
28326
+ resolvedFilepath = path2.join(homeDir, filepath.slice(2));
27961
28327
  }
27962
28328
  let resolvedConfigFilepath = configFilepath;
27963
28329
  if (configFilepath.startsWith(relativeHomeDirPrefix)) {
27964
- resolvedConfigFilepath = path.join(homeDir, configFilepath.slice(2));
28330
+ resolvedConfigFilepath = path2.join(homeDir, configFilepath.slice(2));
27965
28331
  }
27966
28332
  const parsedFiles = await Promise.all([
27967
28333
  readFile2(resolvedConfigFilepath, {
@@ -28045,8 +28411,8 @@ var init_externalDataInterceptor = __esm({
28045
28411
  getFileRecord() {
28046
28412
  return fileIntercept;
28047
28413
  },
28048
- interceptFile(path3, contents) {
28049
- fileIntercept[path3] = Promise.resolve(contents);
28414
+ interceptFile(path4, contents) {
28415
+ fileIntercept[path4] = Promise.resolve(contents);
28050
28416
  },
28051
28417
  getTokenRecord() {
28052
28418
  return tokenIntercept;
@@ -33844,7 +34210,7 @@ Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
33844
34210
  if (token) {
33845
34211
  request2.headers.Authorization = token;
33846
34212
  } else if (tokenFile) {
33847
- request2.headers.Authorization = (await fs__default.default.readFile(tokenFile)).toString();
34213
+ request2.headers.Authorization = (await fs2__default.default.readFile(tokenFile)).toString();
33848
34214
  }
33849
34215
  try {
33850
34216
  const result = await requestHandler.handle(request2);
@@ -34226,10 +34592,10 @@ var init_getNodeModulesParentDirs = __esm({
34226
34592
  if (!dirname2) {
34227
34593
  return [cwd];
34228
34594
  }
34229
- const normalizedPath = path.normalize(dirname2);
34230
- const parts = normalizedPath.split(path.sep);
34595
+ const normalizedPath = path2.normalize(dirname2);
34596
+ const parts = normalizedPath.split(path2.sep);
34231
34597
  const nodeModulesIndex = parts.indexOf("node_modules");
34232
- const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(path.sep) : normalizedPath;
34598
+ const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(path2.sep) : normalizedPath;
34233
34599
  if (cwd === parentDir) {
34234
34600
  return [cwd];
34235
34601
  }
@@ -34281,7 +34647,7 @@ var init_getTypeScriptUserAgentPair = __esm({
34281
34647
  init_getNodeModulesParentDirs();
34282
34648
  init_getSanitizedDevTypeScriptVersion();
34283
34649
  init_getSanitizedTypeScriptVersion();
34284
- TS_PACKAGE_JSON = path.join("node_modules", "typescript", "package.json");
34650
+ TS_PACKAGE_JSON = path2.join("node_modules", "typescript", "package.json");
34285
34651
  getTypeScriptUserAgentPair = async () => {
34286
34652
  if (tscVersion === null) {
34287
34653
  return void 0;
@@ -34302,8 +34668,8 @@ var init_getTypeScriptUserAgentPair = __esm({
34302
34668
  let versionFromApp;
34303
34669
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
34304
34670
  try {
34305
- const appPackageJsonPath = path.join(nodeModulesParentDir, "package.json");
34306
- const packageJson = await fs$1.readFile(appPackageJsonPath, "utf-8");
34671
+ const appPackageJsonPath = path2.join(nodeModulesParentDir, "package.json");
34672
+ const packageJson = await fs2.readFile(appPackageJsonPath, "utf-8");
34307
34673
  const { dependencies, devDependencies } = JSON.parse(packageJson);
34308
34674
  const version = devDependencies?.typescript ?? dependencies?.typescript;
34309
34675
  if (typeof version !== "string") {
@@ -34321,8 +34687,8 @@ var init_getTypeScriptUserAgentPair = __esm({
34321
34687
  let versionFromNodeModules;
34322
34688
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
34323
34689
  try {
34324
- const tsPackageJsonPath = path.join(nodeModulesParentDir, TS_PACKAGE_JSON);
34325
- const packageJson = await fs$1.readFile(tsPackageJsonPath, "utf-8");
34690
+ const tsPackageJsonPath = path2.join(nodeModulesParentDir, TS_PACKAGE_JSON);
34691
+ const packageJson = await fs2.readFile(tsPackageJsonPath, "utf-8");
34326
34692
  const { version } = JSON.parse(packageJson);
34327
34693
  const sanitizedVersion2 = getSanitizedTypeScriptVersion(version);
34328
34694
  if (typeof sanitizedVersion2 !== "string") {
@@ -34465,7 +34831,7 @@ var init_dist_es46 = __esm({
34465
34831
  return Promise.resolve(this.hash.digest());
34466
34832
  }
34467
34833
  reset() {
34468
- this.hash = this.secret ? crypto2.createHmac(this.algorithmIdentifier, castSourceData(this.secret)) : crypto2.createHash(this.algorithmIdentifier);
34834
+ this.hash = this.secret ? cryptoNode.createHmac(this.algorithmIdentifier, castSourceData(this.secret)) : cryptoNode.createHash(this.algorithmIdentifier);
34469
34835
  }
34470
34836
  };
34471
34837
  }
@@ -38690,7 +39056,7 @@ var init_LoginCredentialsFetcher = __esm({
38690
39056
  }
38691
39057
  async saveToken(token) {
38692
39058
  const tokenFilePath = this.getTokenFilePath();
38693
- const directory = path.dirname(tokenFilePath);
39059
+ const directory = path2.dirname(tokenFilePath);
38694
39060
  try {
38695
39061
  await fs.promises.mkdir(directory, { recursive: true });
38696
39062
  } catch (error) {
@@ -38698,10 +39064,10 @@ var init_LoginCredentialsFetcher = __esm({
38698
39064
  await fs.promises.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
38699
39065
  }
38700
39066
  getTokenFilePath() {
38701
- const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? path.join(os.homedir(), ".aws", "login", "cache");
39067
+ const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? path2.join(os.homedir(), ".aws", "login", "cache");
38702
39068
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
38703
- const loginSessionSha256 = crypto2.createHash("sha256").update(loginSessionBytes).digest("hex");
38704
- return path.join(directory, `${loginSessionSha256}.json`);
39069
+ const loginSessionSha256 = cryptoNode.createHash("sha256").update(loginSessionBytes).digest("hex");
39070
+ return path2.join(directory, `${loginSessionSha256}.json`);
38705
39071
  }
38706
39072
  derToRawSignature(derSignature) {
38707
39073
  let offset = 2;
@@ -38745,12 +39111,12 @@ var init_LoginCredentialsFetcher = __esm({
38745
39111
  async generateDpop(method = "POST", endpoint) {
38746
39112
  const token = await this.loadToken();
38747
39113
  try {
38748
- const privateKey = crypto2.createPrivateKey({
39114
+ const privateKey = cryptoNode.createPrivateKey({
38749
39115
  key: token.dpopKey,
38750
39116
  format: "pem",
38751
39117
  type: "sec1"
38752
39118
  });
38753
- const publicKey = crypto2.createPublicKey(privateKey);
39119
+ const publicKey = cryptoNode.createPublicKey(privateKey);
38754
39120
  const publicDer = publicKey.export({ format: "der", type: "spki" });
38755
39121
  let pointStart = -1;
38756
39122
  for (let i6 = 0; i6 < publicDer.length; i6++) {
@@ -38780,7 +39146,7 @@ var init_LoginCredentialsFetcher = __esm({
38780
39146
  const headerB64 = Buffer.from(JSON.stringify(header)).toString("base64url");
38781
39147
  const payloadB64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
38782
39148
  const message = `${headerB64}.${payloadB64}`;
38783
- const asn1Signature = crypto2.sign("sha256", Buffer.from(message), privateKey);
39149
+ const asn1Signature = cryptoNode.sign("sha256", Buffer.from(message), privateKey);
38784
39150
  const rawSignature = this.derToRawSignature(asn1Signature);
38785
39151
  const signatureB64 = rawSignature.toString("base64url");
38786
39152
  return `${message}.${signatureB64}`;
@@ -43316,9 +43682,28 @@ function _getFailStrategy() {
43316
43682
  function _hashPlaintext(plaintext, secret) {
43317
43683
  const trimmed = plaintext.trim();
43318
43684
  if (secret) {
43319
- return crypto2__namespace.createHmac("sha256", secret).update(trimmed, "utf-8").digest("hex");
43685
+ return cryptoNode__namespace.createHmac("sha256", secret).update(trimmed, "utf-8").digest("hex");
43686
+ }
43687
+ return cryptoNode__namespace.createHash("sha256").update(trimmed, "utf-8").digest("hex");
43688
+ }
43689
+ function _vaultKey(token) {
43690
+ return `mask:${config.MASK_TENANT_ID}:${token}`;
43691
+ }
43692
+ function _vaultRevKey(ptHash) {
43693
+ return `mask-rev:${config.MASK_TENANT_ID}:${ptHash}`;
43694
+ }
43695
+ function _vaultHashKey(token) {
43696
+ return `mask-hash:${config.MASK_TENANT_ID}:${token}`;
43697
+ }
43698
+ function _unwrapPayload(raw) {
43699
+ if (raw && raw.startsWith("{")) {
43700
+ try {
43701
+ const obj = JSON.parse(raw);
43702
+ if (obj.ct) return obj.ct;
43703
+ } catch {
43704
+ }
43320
43705
  }
43321
- return crypto2__namespace.createHash("sha256").update(trimmed, "utf-8").digest("hex");
43706
+ return raw;
43322
43707
  }
43323
43708
  function getVault() {
43324
43709
  if (_vaultInstance === null) {
@@ -43359,10 +43744,17 @@ async function encode(rawText, options = {}) {
43359
43744
  return existingToken;
43360
43745
  }
43361
43746
  }
43362
- const token = await generateFPEToken(text, options.entityType || "UNKNOWN");
43747
+ const token = await generateDPToken(text, options.entityType || "UNKNOWN");
43363
43748
  const ciphertext = cryptoEngine.encrypt(text);
43749
+ const existingCiphertext = await vault.retrieve(token);
43750
+ if (existingCiphertext !== null) {
43751
+ const existingHash = await vault.getPtHashForToken(token);
43752
+ if (existingHash && existingHash !== ptHash) {
43753
+ throw new TokenCollisionError(token, existingHash, ptHash);
43754
+ }
43755
+ }
43364
43756
  const ttl = options.ttl || DEFAULT_TTL;
43365
- await vault.store(token, ciphertext, ttl, ptHash);
43757
+ await vault.store(token, ciphertext, ttl, ptHash, options.metadata || null);
43366
43758
  if (options.searchBuckets && options.searchBuckets.length > 0) {
43367
43759
  for (const bType of options.searchBuckets) {
43368
43760
  let bucketVal;
@@ -43384,8 +43776,8 @@ async function decode(token) {
43384
43776
  throw new DecodeError("Token not found or expired");
43385
43777
  }
43386
43778
  try {
43387
- const crypto6 = await getCryptoEngineAsync();
43388
- const result = crypto6.decrypt(ciphertext);
43779
+ const crypto7 = await getCryptoEngineAsync();
43780
+ const result = crypto7.decrypt(ciphertext);
43389
43781
  getAuditLogger().log("decode", token);
43390
43782
  return result;
43391
43783
  } catch (e6) {
@@ -43437,14 +43829,15 @@ var init_vault = __esm({
43437
43829
  MemoryVault = class extends BaseVault {
43438
43830
  constructor() {
43439
43831
  super();
43832
+ this._cleanupTimer = null;
43440
43833
  this._store = /* @__PURE__ */ new Map();
43441
43834
  this._reverseStore = /* @__PURE__ */ new Map();
43835
+ this._cleanupTimer = setInterval(() => this._cleanup(), 6e4);
43836
+ if (this._cleanupTimer && typeof this._cleanupTimer.unref === "function") {
43837
+ this._cleanupTimer.unref();
43838
+ }
43442
43839
  }
43443
43840
  _cleanup() {
43444
- const cleanupFreq = config.MASK_VAULT_CLEANUP_FREQUENCY;
43445
- if (Math.random() > cleanupFreq) {
43446
- return;
43447
- }
43448
43841
  const now = Date.now() / 1e3;
43449
43842
  for (const [token, entry] of this._store.entries()) {
43450
43843
  if (now > entry.expiry) {
@@ -43455,27 +43848,41 @@ var init_vault = __esm({
43455
43848
  }
43456
43849
  }
43457
43850
  }
43458
- async store(token, plaintext, ttlSeconds, ptHash = null) {
43459
- this._cleanup();
43851
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43852
+ if (!this._store.has(token) && this._store.size >= config.MASK_VAULT_MAX_MEMORY_KEYS) {
43853
+ const firstKey = this._store.keys().next().value;
43854
+ if (firstKey !== void 0) {
43855
+ const oldEntry = this._store.get(firstKey);
43856
+ this._store.delete(firstKey);
43857
+ if (oldEntry?.ptHash && this._reverseStore.get(oldEntry.ptHash) === firstKey) {
43858
+ this._reverseStore.delete(oldEntry.ptHash);
43859
+ }
43860
+ }
43861
+ }
43862
+ const existing = this._store.get(token);
43863
+ if (existing) this._store.delete(token);
43460
43864
  this._store.set(token, {
43461
- plaintext,
43865
+ plaintext: ciphertext,
43462
43866
  expiry: Date.now() / 1e3 + ttlSeconds,
43463
- ptHash
43867
+ ptHash,
43868
+ metadata: { ...existing?.metadata || {}, ...metadata || {} }
43464
43869
  });
43465
43870
  if (ptHash) {
43466
43871
  this._reverseStore.set(ptHash, token);
43467
43872
  }
43468
43873
  }
43469
43874
  async getTokenByPlaintextHash(ptHash) {
43470
- this._cleanup();
43471
43875
  const token = this._reverseStore.get(ptHash);
43472
43876
  if (token && this._store.has(token)) {
43473
43877
  return token;
43474
43878
  }
43475
43879
  return null;
43476
43880
  }
43881
+ async getPtHashForToken(token) {
43882
+ const entry = this._store.get(token);
43883
+ return entry?.ptHash ?? null;
43884
+ }
43477
43885
  async retrieve(token) {
43478
- this._cleanup();
43479
43886
  const entry = this._store.get(token);
43480
43887
  if (!entry) {
43481
43888
  return null;
@@ -43531,13 +43938,14 @@ var init_vault = __esm({
43531
43938
  throw new exports.MaskVaultConnectionError(`Failed to connect to Redis: ${e6}`);
43532
43939
  }
43533
43940
  }
43534
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
43941
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43535
43942
  try {
43536
43943
  const pipeline = this._client.pipeline();
43537
- pipeline.set(`mask:${token}`, ciphertext, "EX", ttlSeconds);
43944
+ const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
43945
+ pipeline.set(_vaultKey(token), payload, "EX", ttlSeconds);
43538
43946
  if (ptHash) {
43539
- pipeline.set(`mask-rev:${ptHash}`, token, "EX", ttlSeconds);
43540
- pipeline.set(`mask-hash:${token}`, ptHash, "EX", ttlSeconds);
43947
+ pipeline.set(_vaultRevKey(ptHash), token, "EX", ttlSeconds);
43948
+ pipeline.set(_vaultHashKey(token), ptHash, "EX", ttlSeconds);
43541
43949
  }
43542
43950
  const results = await pipeline.exec();
43543
43951
  if (results) {
@@ -43549,14 +43957,21 @@ var init_vault = __esm({
43549
43957
  throw new exports.MaskVaultConnectionError(`Redis error: ${e6}`);
43550
43958
  }
43551
43959
  }
43960
+ async getPtHashForToken(token) {
43961
+ try {
43962
+ return await this._client.get(_vaultHashKey(token));
43963
+ } catch {
43964
+ return null;
43965
+ }
43966
+ }
43552
43967
  async getTokenByPlaintextHash(ptHash) {
43553
43968
  try {
43554
- const token = await this._client.get(`mask-rev:${ptHash}`);
43969
+ const token = await this._client.get(_vaultRevKey(ptHash));
43555
43970
  if (token) {
43556
- if (await this._client.exists(`mask:${token}`)) {
43971
+ if (await this._client.exists(_vaultKey(token))) {
43557
43972
  return token;
43558
43973
  } else {
43559
- await this._client.del(`mask-rev:${ptHash}`);
43974
+ await this._client.del(_vaultRevKey(ptHash));
43560
43975
  }
43561
43976
  }
43562
43977
  return null;
@@ -43569,7 +43984,8 @@ var init_vault = __esm({
43569
43984
  }
43570
43985
  async retrieve(token) {
43571
43986
  try {
43572
- return await this._client.get(`mask:${token}`);
43987
+ const raw = await this._client.get(_vaultKey(token));
43988
+ return raw ? _unwrapPayload(raw) : null;
43573
43989
  } catch (e6) {
43574
43990
  if (_getFailStrategy() === "closed") {
43575
43991
  throw new exports.MaskVaultConnectionError(`Redis read failed: ${e6}`);
@@ -43579,12 +43995,12 @@ var init_vault = __esm({
43579
43995
  }
43580
43996
  async delete(token) {
43581
43997
  try {
43582
- const ptHash = await this._client.get(`mask-hash:${token}`);
43998
+ const ptHash = await this._client.get(_vaultHashKey(token));
43583
43999
  const pipeline = this._client.pipeline();
43584
- pipeline.del(`mask:${token}`);
43585
- pipeline.del(`mask-hash:${token}`);
44000
+ pipeline.del(_vaultKey(token));
44001
+ pipeline.del(_vaultHashKey(token));
43586
44002
  if (ptHash) {
43587
- pipeline.del(`mask-rev:${ptHash}`);
44003
+ pipeline.del(_vaultRevKey(ptHash));
43588
44004
  }
43589
44005
  await pipeline.exec();
43590
44006
  } catch (e6) {
@@ -43623,39 +44039,26 @@ var init_vault = __esm({
43623
44039
  this._client = DynamoDBDocument.from(baseClient);
43624
44040
  console.info(`DynamoDBVault connected to table ${this._tableName} in ${this._region}`);
43625
44041
  }
43626
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
44042
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43627
44043
  const { TransactWriteCommand, PutCommand } = __require("@aws-sdk/lib-dynamodb");
43628
44044
  const now = Math.floor(Date.now() / 1e3);
43629
44045
  const ttlVal = now + ttlSeconds;
43630
- const item = {
43631
- token: `mask:${token}`,
44046
+ const primaryItem = {
44047
+ token: _vaultKey(token),
43632
44048
  ciphertext,
43633
- ttl: ttlVal,
43634
- ptr_hash: ptHash || void 0
44049
+ ttl: ttlVal
43635
44050
  };
44051
+ if (ptHash) primaryItem.ptr_hash = ptHash;
44052
+ if (metadata) primaryItem.meta_json = JSON.stringify(metadata);
43636
44053
  if (ptHash) {
43637
44054
  try {
43638
44055
  await this._client.send(new TransactWriteCommand({
43639
44056
  TransactItems: [
44057
+ { Put: { TableName: this._tableName, Item: primaryItem } },
43640
44058
  {
43641
44059
  Put: {
43642
44060
  TableName: this._tableName,
43643
- Item: {
43644
- token: `mask:${token}`,
43645
- ciphertext,
43646
- ttl: ttlVal,
43647
- ptr_hash: ptHash
43648
- }
43649
- }
43650
- },
43651
- {
43652
- Put: {
43653
- TableName: this._tableName,
43654
- Item: {
43655
- token: `mask-rev:${ptHash}`,
43656
- ciphertext: token,
43657
- ttl: ttlVal
43658
- }
44061
+ Item: { token: _vaultRevKey(ptHash), ciphertext: token, ttl: ttlVal }
43659
44062
  }
43660
44063
  }
43661
44064
  ]
@@ -43666,7 +44069,7 @@ var init_vault = __esm({
43666
44069
  }
43667
44070
  } else {
43668
44071
  try {
43669
- await this._client.send(new PutCommand({ TableName: this._tableName, Item: item }));
44072
+ await this._client.send(new PutCommand({ TableName: this._tableName, Item: primaryItem }));
43670
44073
  } catch (e6) {
43671
44074
  throw new exports.MaskVaultConnectionError(`DynamoDB individual write failed: ${e6}`);
43672
44075
  }
@@ -43678,12 +44081,12 @@ var init_vault = __esm({
43678
44081
  const now = Math.floor(Date.now() / 1e3);
43679
44082
  const resp = await this._client.send(new GetCommand({
43680
44083
  TableName: this._tableName,
43681
- Key: { token: `mask-rev:${ptHash}` }
44084
+ Key: { token: _vaultRevKey(ptHash) }
43682
44085
  }));
43683
44086
  const item = resp.Item;
43684
44087
  if (!item) return null;
43685
44088
  if (now > (item.ttl || 0)) {
43686
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${ptHash}` } }));
44089
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(ptHash) } }));
43687
44090
  return null;
43688
44091
  }
43689
44092
  const token = item.ciphertext;
@@ -43699,16 +44102,16 @@ var init_vault = __esm({
43699
44102
  const now = Math.floor(Date.now() / 1e3);
43700
44103
  const resp = await this._client.send(new GetCommand({
43701
44104
  TableName: this._tableName,
43702
- Key: { token: `mask:${token}` }
44105
+ Key: { token: _vaultKey(token) }
43703
44106
  }));
43704
44107
  const item = resp.Item;
43705
44108
  if (!item) return null;
43706
44109
  if (now > (item.ttl || 0)) {
43707
44110
  const ptHash = item.ptr_hash;
43708
44111
  if (ptHash) {
43709
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${ptHash}` } }));
44112
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(ptHash) } }));
43710
44113
  }
43711
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask:${token}` } }));
44114
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultKey(token) } }));
43712
44115
  return null;
43713
44116
  }
43714
44117
  return item.ciphertext;
@@ -43717,18 +44120,30 @@ var init_vault = __esm({
43717
44120
  return null;
43718
44121
  }
43719
44122
  }
44123
+ async getPtHashForToken(token) {
44124
+ try {
44125
+ const { GetCommand } = __require("@aws-sdk/lib-dynamodb");
44126
+ const resp = await this._client.send(new GetCommand({
44127
+ TableName: this._tableName,
44128
+ Key: { token: _vaultKey(token) }
44129
+ }));
44130
+ return resp.Item?.ptr_hash ?? null;
44131
+ } catch {
44132
+ return null;
44133
+ }
44134
+ }
43720
44135
  async delete(token) {
43721
44136
  try {
43722
44137
  const { GetCommand, DeleteCommand } = __require("@aws-sdk/lib-dynamodb");
43723
44138
  const resp = await this._client.send(new GetCommand({
43724
44139
  TableName: this._tableName,
43725
- Key: { token: `mask:${token}` }
44140
+ Key: { token: _vaultKey(token) }
43726
44141
  }));
43727
44142
  const item = resp.Item;
43728
44143
  if (item && item.ptr_hash) {
43729
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${item.ptr_hash}` } }));
44144
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(item.ptr_hash) } }));
43730
44145
  }
43731
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask:${token}` } }));
44146
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultKey(token) } }));
43732
44147
  } catch (e6) {
43733
44148
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`DynamoDB delete failed: ${e6}`);
43734
44149
  }
@@ -43747,20 +44162,29 @@ var init_vault = __esm({
43747
44162
  throw new exports.MaskVaultConnectionError(`Failed to connect to Memcached: ${e6}`);
43748
44163
  }
43749
44164
  }
43750
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
44165
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43751
44166
  try {
43752
- await this._client.set(`mask:${token}`, Buffer.from(ciphertext), { expires: ttlSeconds });
44167
+ const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
44168
+ await this._client.set(_vaultKey(token), Buffer.from(payload), { expires: ttlSeconds });
43753
44169
  if (ptHash) {
43754
- await this._client.set(`mask-rev:${ptHash}`, Buffer.from(token), { expires: ttlSeconds });
43755
- await this._client.set(`mask-hash:${token}`, Buffer.from(ptHash), { expires: ttlSeconds });
44170
+ await this._client.set(_vaultRevKey(ptHash), Buffer.from(token), { expires: ttlSeconds });
44171
+ await this._client.set(_vaultHashKey(token), Buffer.from(ptHash), { expires: ttlSeconds });
43756
44172
  }
43757
44173
  } catch (e6) {
43758
44174
  throw new exports.MaskVaultConnectionError(`Memcached error: ${e6}`);
43759
44175
  }
43760
44176
  }
44177
+ async getPtHashForToken(token) {
44178
+ try {
44179
+ const { value } = await this._client.get(_vaultHashKey(token));
44180
+ return value ? value.toString() : null;
44181
+ } catch {
44182
+ return null;
44183
+ }
44184
+ }
43761
44185
  async getTokenByPlaintextHash(ptHash) {
43762
44186
  try {
43763
- const { value } = await this._client.get(`mask-rev:${ptHash}`);
44187
+ const { value } = await this._client.get(_vaultRevKey(ptHash));
43764
44188
  if (!value) return null;
43765
44189
  const token = value.toString();
43766
44190
  return await this.retrieve(token) !== null ? token : null;
@@ -43771,8 +44195,9 @@ var init_vault = __esm({
43771
44195
  }
43772
44196
  async retrieve(token) {
43773
44197
  try {
43774
- const { value } = await this._client.get(`mask:${token}`);
43775
- return value ? value.toString() : null;
44198
+ const { value } = await this._client.get(_vaultKey(token));
44199
+ if (!value) return null;
44200
+ return _unwrapPayload(value.toString());
43776
44201
  } catch (e6) {
43777
44202
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`Memcached read failed: ${e6}`);
43778
44203
  return null;
@@ -43780,12 +44205,12 @@ var init_vault = __esm({
43780
44205
  }
43781
44206
  async delete(token) {
43782
44207
  try {
43783
- const { value } = await this._client.get(`mask-hash:${token}`);
44208
+ const { value } = await this._client.get(_vaultHashKey(token));
43784
44209
  const ptHash = value ? value.toString() : null;
43785
- await this._client.delete(`mask:${token}`);
43786
- await this._client.delete(`mask-hash:${token}`);
44210
+ await this._client.delete(_vaultKey(token));
44211
+ await this._client.delete(_vaultHashKey(token));
43787
44212
  if (ptHash) {
43788
- await this._client.delete(`mask-rev:${ptHash}`);
44213
+ await this._client.delete(_vaultRevKey(ptHash));
43789
44214
  }
43790
44215
  } catch (e6) {
43791
44216
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`Memcached delete failed: ${e6}`);
@@ -53503,11 +53928,11 @@ var require_mime_types = __commonJS({
53503
53928
  }
53504
53929
  return exts[0];
53505
53930
  }
53506
- function lookup(path3) {
53507
- if (!path3 || typeof path3 !== "string") {
53931
+ function lookup(path4) {
53932
+ if (!path4 || typeof path4 !== "string") {
53508
53933
  return false;
53509
53934
  }
53510
- var extension2 = extname("x." + path3).toLowerCase().substr(1);
53935
+ var extension2 = extname("x." + path4).toLowerCase().substr(1);
53511
53936
  if (!extension2) {
53512
53937
  return false;
53513
53938
  }
@@ -54574,13 +54999,13 @@ var require_form_data = __commonJS({
54574
54999
  "node_modules/form-data/lib/form_data.js"(exports2, module) {
54575
55000
  var CombinedStream = require_combined_stream();
54576
55001
  var util = __require("util");
54577
- var path3 = __require("path");
55002
+ var path4 = __require("path");
54578
55003
  var http = __require("http");
54579
55004
  var https = __require("https");
54580
55005
  var parseUrl2 = __require("url").parse;
54581
- var fs3 = __require("fs");
55006
+ var fs4 = __require("fs");
54582
55007
  var Stream = __require("stream").Stream;
54583
- var crypto6 = __require("crypto");
55008
+ var crypto7 = __require("crypto");
54584
55009
  var mime = require_mime_types();
54585
55010
  var asynckit = require_asynckit();
54586
55011
  var setToStringTag = require_es_set_tostringtag();
@@ -54645,7 +55070,7 @@ var require_form_data = __commonJS({
54645
55070
  if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
54646
55071
  callback(null, value.end + 1 - (value.start ? value.start : 0));
54647
55072
  } else {
54648
- fs3.stat(value.path, function(err2, stat) {
55073
+ fs4.stat(value.path, function(err2, stat) {
54649
55074
  if (err2) {
54650
55075
  callback(err2);
54651
55076
  return;
@@ -54702,11 +55127,11 @@ var require_form_data = __commonJS({
54702
55127
  FormData2.prototype._getContentDisposition = function(value, options) {
54703
55128
  var filename;
54704
55129
  if (typeof options.filepath === "string") {
54705
- filename = path3.normalize(options.filepath).replace(/\\/g, "/");
55130
+ filename = path4.normalize(options.filepath).replace(/\\/g, "/");
54706
55131
  } else if (options.filename || value && (value.name || value.path)) {
54707
- filename = path3.basename(options.filename || value && (value.name || value.path));
55132
+ filename = path4.basename(options.filename || value && (value.name || value.path));
54708
55133
  } else if (value && value.readable && hasOwn(value, "httpVersion")) {
54709
- filename = path3.basename(value.client._httpMessage.path || "");
55134
+ filename = path4.basename(value.client._httpMessage.path || "");
54710
55135
  }
54711
55136
  if (filename) {
54712
55137
  return 'filename="' + filename + '"';
@@ -54786,7 +55211,7 @@ var require_form_data = __commonJS({
54786
55211
  return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]);
54787
55212
  };
54788
55213
  FormData2.prototype._generateBoundary = function() {
54789
- this._boundary = "--------------------------" + crypto6.randomBytes(12).toString("hex");
55214
+ this._boundary = "--------------------------" + crypto7.randomBytes(12).toString("hex");
54790
55215
  };
54791
55216
  FormData2.prototype.getLengthSync = function() {
54792
55217
  var knownLength = this._overheadLength + this._valueLength;
@@ -55476,7 +55901,7 @@ var require_follow_redirects = __commonJS({
55476
55901
  var require_axios = __commonJS({
55477
55902
  "node_modules/axios/dist/node/axios.cjs"(exports2, module) {
55478
55903
  var FormData$1 = require_form_data();
55479
- var crypto6 = __require("crypto");
55904
+ var crypto7 = __require("crypto");
55480
55905
  var url = __require("url");
55481
55906
  var proxyFromEnv = require_proxy_from_env();
55482
55907
  var http = __require("http");
@@ -55491,7 +55916,7 @@ var require_axios = __commonJS({
55491
55916
  return e6 && typeof e6 === "object" && "default" in e6 ? e6 : { "default": e6 };
55492
55917
  }
55493
55918
  var FormData__default = /* @__PURE__ */ _interopDefaultLegacy(FormData$1);
55494
- var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto6);
55919
+ var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto7);
55495
55920
  var url__default = /* @__PURE__ */ _interopDefaultLegacy(url);
55496
55921
  var proxyFromEnv__default = /* @__PURE__ */ _interopDefaultLegacy(proxyFromEnv);
55497
55922
  var http__default = /* @__PURE__ */ _interopDefaultLegacy(http);
@@ -56010,9 +56435,9 @@ var require_axios = __commonJS({
56010
56435
  function removeBrackets(key) {
56011
56436
  return utils$1.endsWith(key, "[]") ? key.slice(0, -2) : key;
56012
56437
  }
56013
- function renderKey(path3, key, dots) {
56014
- if (!path3) return key;
56015
- return path3.concat(key).map(function each(token, i6) {
56438
+ function renderKey(path4, key, dots) {
56439
+ if (!path4) return key;
56440
+ return path4.concat(key).map(function each(token, i6) {
56016
56441
  token = removeBrackets(token);
56017
56442
  return !dots && i6 ? "[" + token + "]" : token;
56018
56443
  }).join(dots ? "." : "");
@@ -56065,13 +56490,13 @@ var require_axios = __commonJS({
56065
56490
  }
56066
56491
  return value;
56067
56492
  }
56068
- function defaultVisitor(value, key, path3) {
56493
+ function defaultVisitor(value, key, path4) {
56069
56494
  let arr = value;
56070
56495
  if (utils$1.isReactNative(formData) && utils$1.isReactNativeBlob(value)) {
56071
- formData.append(renderKey(path3, key, dots), convertValue(value));
56496
+ formData.append(renderKey(path4, key, dots), convertValue(value));
56072
56497
  return false;
56073
56498
  }
56074
- if (value && !path3 && typeof value === "object") {
56499
+ if (value && !path4 && typeof value === "object") {
56075
56500
  if (utils$1.endsWith(key, "{}")) {
56076
56501
  key = metaTokens ? key : key.slice(0, -2);
56077
56502
  value = JSON.stringify(value);
@@ -56090,7 +56515,7 @@ var require_axios = __commonJS({
56090
56515
  if (isVisitable(value)) {
56091
56516
  return true;
56092
56517
  }
56093
- formData.append(renderKey(path3, key, dots), convertValue(value));
56518
+ formData.append(renderKey(path4, key, dots), convertValue(value));
56094
56519
  return false;
56095
56520
  }
56096
56521
  const stack = [];
@@ -56099,16 +56524,16 @@ var require_axios = __commonJS({
56099
56524
  convertValue,
56100
56525
  isVisitable
56101
56526
  });
56102
- function build(value, path3) {
56527
+ function build(value, path4) {
56103
56528
  if (utils$1.isUndefined(value)) return;
56104
56529
  if (stack.indexOf(value) !== -1) {
56105
- throw Error("Circular reference detected in " + path3.join("."));
56530
+ throw Error("Circular reference detected in " + path4.join("."));
56106
56531
  }
56107
56532
  stack.push(value);
56108
56533
  utils$1.forEach(value, function each(el, key) {
56109
- const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path3, exposedHelpers);
56534
+ const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path4, exposedHelpers);
56110
56535
  if (result === true) {
56111
- build(el, path3 ? path3.concat(key) : [key]);
56536
+ build(el, path4 ? path4.concat(key) : [key]);
56112
56537
  }
56113
56538
  });
56114
56539
  stack.pop();
@@ -56296,7 +56721,7 @@ var require_axios = __commonJS({
56296
56721
  };
56297
56722
  function toURLEncodedForm(data, options) {
56298
56723
  return toFormData(data, new platform2.classes.URLSearchParams(), {
56299
- visitor: function(value, key, path3, helpers) {
56724
+ visitor: function(value, key, path4, helpers) {
56300
56725
  if (platform2.isNode && utils$1.isBuffer(value)) {
56301
56726
  this.append(key, value.toString("base64"));
56302
56727
  return false;
@@ -56324,11 +56749,11 @@ var require_axios = __commonJS({
56324
56749
  return obj;
56325
56750
  }
56326
56751
  function formDataToJSON(formData) {
56327
- function buildPath(path3, value, target, index) {
56328
- let name = path3[index++];
56752
+ function buildPath(path4, value, target, index) {
56753
+ let name = path4[index++];
56329
56754
  if (name === "__proto__") return true;
56330
56755
  const isNumericKey = Number.isFinite(+name);
56331
- const isLast = index >= path3.length;
56756
+ const isLast = index >= path4.length;
56332
56757
  name = !name && utils$1.isArray(target) ? target.length : name;
56333
56758
  if (isLast) {
56334
56759
  if (utils$1.hasOwnProp(target, name)) {
@@ -56341,7 +56766,7 @@ var require_axios = __commonJS({
56341
56766
  if (!target[name] || !utils$1.isObject(target[name])) {
56342
56767
  target[name] = [];
56343
56768
  }
56344
- const result = buildPath(path3, value, target[name], index);
56769
+ const result = buildPath(path4, value, target[name], index);
56345
56770
  if (result && utils$1.isArray(target[name])) {
56346
56771
  target[name] = arrayToObject(target[name]);
56347
56772
  }
@@ -57652,9 +58077,9 @@ var require_axios = __commonJS({
57652
58077
  auth = urlUsername + ":" + urlPassword;
57653
58078
  }
57654
58079
  auth && headers.delete("authorization");
57655
- let path3;
58080
+ let path4;
57656
58081
  try {
57657
- path3 = buildURL(
58082
+ path4 = buildURL(
57658
58083
  parsed.pathname + parsed.search,
57659
58084
  config2.params,
57660
58085
  config2.paramsSerializer
@@ -57672,7 +58097,7 @@ var require_axios = __commonJS({
57672
58097
  false
57673
58098
  );
57674
58099
  const options = {
57675
- path: path3,
58100
+ path: path4,
57676
58101
  method,
57677
58102
  headers: headers.toJSON(),
57678
58103
  agents: { http: config2.httpAgent, https: config2.httpsAgent },
@@ -57917,14 +58342,14 @@ var require_axios = __commonJS({
57917
58342
  var cookies = platform2.hasStandardBrowserEnv ? (
57918
58343
  // Standard browser envs support document.cookie
57919
58344
  {
57920
- write(name, value, expires, path3, domain, secure, sameSite) {
58345
+ write(name, value, expires, path4, domain, secure, sameSite) {
57921
58346
  if (typeof document === "undefined") return;
57922
58347
  const cookie = [`${name}=${encodeURIComponent(value)}`];
57923
58348
  if (utils$1.isNumber(expires)) {
57924
58349
  cookie.push(`expires=${new Date(expires).toUTCString()}`);
57925
58350
  }
57926
- if (utils$1.isString(path3)) {
57927
- cookie.push(`path=${path3}`);
58351
+ if (utils$1.isString(path4)) {
58352
+ cookie.push(`path=${path4}`);
57928
58353
  }
57929
58354
  if (utils$1.isString(domain)) {
57930
58355
  cookie.push(`domain=${domain}`);
@@ -59270,7 +59695,7 @@ var init_transformers_scanner = __esm({
59270
59695
  );
59271
59696
  }
59272
59697
  if (!this._pool) {
59273
- const workerPath = path__namespace.resolve(__dirname, "nlp_worker.js");
59698
+ const workerPath = path2__namespace.resolve(__dirname, "nlp_worker.js");
59274
59699
  const maxThreads = Math.max(1, Math.min(os__namespace.cpus().length - 1, 4));
59275
59700
  this._pool = new Piscina({
59276
59701
  filename: workerPath,
@@ -59867,12 +60292,12 @@ var MaskClient = class _MaskClient {
59867
60292
  */
59868
60293
  static async create(options = {}) {
59869
60294
  const vault = options.vault || getVault();
59870
- const crypto6 = options.crypto || await getCryptoEngineAsync();
60295
+ const crypto7 = options.crypto || await getCryptoEngineAsync();
59871
60296
  const scanner = options.scanner || getScanner();
59872
60297
  const auditLogger = options.auditLogger || getAuditLogger();
59873
60298
  return new _MaskClient({
59874
60299
  vault,
59875
- crypto: crypto6,
60300
+ crypto: crypto7,
59876
60301
  scanner,
59877
60302
  auditLogger,
59878
60303
  ttl: options.ttl
@@ -59893,7 +60318,7 @@ var MaskClient = class _MaskClient {
59893
60318
  this.logger.log("encode", existingToken, "opaque");
59894
60319
  return existingToken;
59895
60320
  }
59896
- const token = await generateFPEToken(text);
60321
+ const token = await exports.generateFPEToken(text);
59897
60322
  const ciphertext = this.crypto.encrypt(text);
59898
60323
  await this.vault.store(token, ciphertext, this.ttl, ptHash);
59899
60324
  this.logger.log("encode", token);
@@ -60017,7 +60442,6 @@ exports.decode = decode;
60017
60442
  exports.detectEntitiesWithConfidence = detectEntitiesWithConfidence;
60018
60443
  exports.detokenizeText = detokenizeText;
60019
60444
  exports.encode = encode;
60020
- exports.generateFPEToken = generateFPEToken;
60021
60445
  exports.getScanner = getScanner;
60022
60446
  exports.getVault = getVault;
60023
60447
  exports.looksLikeToken = looksLikeToken;