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.mjs CHANGED
@@ -1,19 +1,20 @@
1
1
  import * as process2 from 'process';
2
2
  import { env, versions } from 'process';
3
- import * as path from 'path';
3
+ import * as path2 from 'path';
4
4
  import { join, normalize, sep, dirname } from 'path';
5
5
  import * as os from 'os';
6
6
  import { platform, release, homedir } from 'os';
7
- import * as crypto2 from 'crypto';
8
- import crypto2__default, { createHmac, createHash, createPrivateKey, createPublicKey, sign } from 'crypto';
7
+ import * as cryptoNode from 'crypto';
8
+ import cryptoNode__default, { createHmac, createHash, createPrivateKey, createPublicKey, sign } from 'crypto';
9
+ import * as fs from 'fs';
10
+ import { ReadStream, lstatSync, fstatSync, readFileSync, promises } from 'fs';
9
11
  import { Buffer as Buffer$1 } from 'buffer';
10
12
  import { Writable, Readable } from 'stream';
11
13
  import { Agent, request as request$1 } from 'https';
12
14
  import http2, { constants } from 'http2';
13
- import fs, { readFile } from 'fs/promises';
15
+ import fs2, { readFile } from 'fs/promises';
14
16
  import { request } from 'http';
15
17
  import { parse } from 'url';
16
- import { ReadStream, lstatSync, fstatSync, readFileSync, promises } from 'fs';
17
18
  import { exec } from 'child_process';
18
19
  import { promisify } from 'util';
19
20
 
@@ -103,6 +104,11 @@ var init_config = __esm({
103
104
  get MASK_ENCRYPTION_KEY() {
104
105
  return process2.env.MASK_ENCRYPTION_KEY || null;
105
106
  },
107
+ // JSON map of keyId -> base64 key string for key rotation, e.g. {"v1":"...","v2":"..."}
108
+ // The last entry in the map is treated as the active (encryption) key.
109
+ get MASK_KEYRING() {
110
+ return process2.env.MASK_KEYRING || null;
111
+ },
106
112
  get MASK_MASTER_KEY() {
107
113
  return process2.env.MASK_MASTER_KEY || process2.env.MASK_ENCRYPTION_KEY || "";
108
114
  },
@@ -115,6 +121,9 @@ var init_config = __esm({
115
121
  get MASK_BLIND_INDEX_SALT() {
116
122
  return process2.env.MASK_BLIND_INDEX_SALT || "mask-blind-index";
117
123
  },
124
+ get MASK_KDF_SALT() {
125
+ return process2.env.MASK_KDF_SALT || "mask-kdf-v4-argon2id";
126
+ },
118
127
  get VAULT_TOKEN() {
119
128
  return process2.env.VAULT_TOKEN || null;
120
129
  },
@@ -131,6 +140,9 @@ var init_config = __esm({
131
140
  get MASK_VAULT_CLEANUP_FREQUENCY() {
132
141
  return getEnvFloat("MASK_VAULT_CLEANUP_FREQUENCY", 0.01);
133
142
  },
143
+ get MASK_VAULT_MAX_MEMORY_KEYS() {
144
+ return getEnvInt("MASK_VAULT_MAX_MEMORY_KEYS", 1e5);
145
+ },
134
146
  // --- BACKEND CONNECTIONS ---
135
147
  get MASK_REDIS_URL() {
136
148
  return process2.env.MASK_REDIS_URL || "redis://localhost:6379/0";
@@ -170,7 +182,7 @@ var init_config = __esm({
170
182
  return process2.env.MASK_SCANNER_URL || "http://localhost:5001/analyze";
171
183
  },
172
184
  get MASK_MODEL_CACHE_DIR() {
173
- return process2.env.MASK_MODEL_CACHE_DIR || path.join(os.homedir(), ".cache", "mask");
185
+ return process2.env.MASK_MODEL_CACHE_DIR || path2.join(os.homedir(), ".cache", "mask");
174
186
  },
175
187
  // --- TELEMETRY & AUDIT ---
176
188
  get MASK_AUDIT_LOG_STRICT() {
@@ -195,6 +207,16 @@ var init_key_provider = __esm({
195
207
  "src/core/key_provider.ts"() {
196
208
  init_config();
197
209
  BaseKeyProvider = class {
210
+ /**
211
+ * Return a JSON keyring string (e.g. from KMS / Secrets Manager), or null
212
+ * to fall back to the MASK_KEYRING environment variable.
213
+ *
214
+ * Override in KMS-backed providers to source the full keyring from a
215
+ * secure external store, removing the need for MASK_KEYRING in env vars.
216
+ */
217
+ getKeyring() {
218
+ return null;
219
+ }
198
220
  };
199
221
  EnvKeyProvider = class extends BaseKeyProvider {
200
222
  async getEncryptionKey() {
@@ -210,13 +232,17 @@ var init_key_provider = __esm({
210
232
  let key = config.MASK_MASTER_KEY;
211
233
  return key || null;
212
234
  }
235
+ /** Return MASK_KEYRING from environment (default behaviour). */
236
+ async getKeyring() {
237
+ return config.MASK_KEYRING || null;
238
+ }
213
239
  };
214
240
  providerInstance = null;
215
241
  }
216
242
  });
217
243
 
218
244
  // src/core/exceptions.ts
219
- var MaskError, MaskVaultConnectionError, MaskDecryptionError, MaskNLPTimeout, MaskSecurityError;
245
+ var MaskError, MaskVaultConnectionError, MaskDecryptionError, MaskNLPTimeout, MaskSecurityError, TokenCollisionError;
220
246
  var init_exceptions = __esm({
221
247
  "src/core/exceptions.ts"() {
222
248
  MaskError = class extends Error {
@@ -234,6 +260,185 @@ var init_exceptions = __esm({
234
260
  };
235
261
  MaskSecurityError = class extends MaskError {
236
262
  };
263
+ TokenCollisionError = class extends MaskError {
264
+ constructor(token, existingHash, incomingHash) {
265
+ super(
266
+ `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.`
267
+ );
268
+ this.token = token;
269
+ this.existingHash = existingHash;
270
+ this.incomingHash = incomingHash;
271
+ }
272
+ };
273
+ }
274
+ });
275
+ var FF1;
276
+ var init_ff1 = __esm({
277
+ "src/core/ff1.ts"() {
278
+ FF1 = class {
279
+ constructor(key, tweak, radix) {
280
+ this.chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
281
+ this.key = key;
282
+ this.tweak = tweak;
283
+ this.radix = radix;
284
+ if (radix > this.chars.length) {
285
+ throw new Error(`Radix ${radix} not supported`);
286
+ }
287
+ }
288
+ _prf(x6) {
289
+ const cipher = cryptoNode.createCipheriv("aes-256-cbc", this.key, Buffer.alloc(16, 0));
290
+ cipher.setAutoPadding(false);
291
+ return Buffer.concat([cipher.update(x6), cipher.final()]).subarray(-16);
292
+ }
293
+ _ciph(x6) {
294
+ const cipher = cryptoNode.createCipheriv("aes-256-ecb", this.key, null);
295
+ cipher.setAutoPadding(false);
296
+ return Buffer.concat([cipher.update(x6), cipher.final()]);
297
+ }
298
+ _strToInt(s6) {
299
+ let n6 = 0n;
300
+ const r6 = BigInt(this.radix);
301
+ for (let i6 = 0; i6 < s6.length; i6++) {
302
+ n6 = n6 * r6 + BigInt(this.chars.indexOf(s6[i6]));
303
+ }
304
+ return n6;
305
+ }
306
+ _intToStr(num, length) {
307
+ if (num === 0n) {
308
+ return this.chars[0].repeat(length);
309
+ }
310
+ let digits = [];
311
+ let n6 = num;
312
+ const r6 = BigInt(this.radix);
313
+ while (n6 > 0n) {
314
+ digits.push(this.chars[Number(n6 % r6)]);
315
+ n6 /= r6;
316
+ }
317
+ let s6 = digits.reverse().join("");
318
+ while (s6.length < length) s6 = this.chars[0] + s6;
319
+ return s6;
320
+ }
321
+ _bigintToBuffer(num, bytes) {
322
+ const buf = Buffer.alloc(bytes);
323
+ let n6 = num;
324
+ for (let i6 = bytes - 1; i6 >= 0; i6--) {
325
+ buf[i6] = Number(n6 & 0xFFn);
326
+ n6 >>= 8n;
327
+ }
328
+ return buf;
329
+ }
330
+ encrypt(X) {
331
+ const n6 = X.length;
332
+ const t6 = this.tweak.length;
333
+ if (n6 < 2) return X;
334
+ const u6 = Math.floor(n6 / 2);
335
+ const v7 = n6 - u6;
336
+ let A3 = X.substring(0, u6);
337
+ let B3 = X.substring(u6);
338
+ const b6 = Math.ceil(Math.ceil(v7 * Math.log2(this.radix)) / 8);
339
+ const d6 = 4 * Math.ceil(b6 / 4) + 4;
340
+ const P2 = Buffer.alloc(16);
341
+ P2[0] = 1;
342
+ P2[1] = 2;
343
+ P2[2] = 1;
344
+ P2.writeUIntBE(this.radix, 3, 3);
345
+ P2[6] = 10;
346
+ P2[7] = u6 % 256;
347
+ P2.writeUInt32BE(n6, 8);
348
+ P2.writeUInt32BE(t6, 12);
349
+ for (let i6 = 0; i6 < 10; i6++) {
350
+ const m6 = i6 % 2 === 0 ? u6 : v7;
351
+ const padLen = ((-t6 - b6 - 1) % 16 + 16) % 16;
352
+ const Q2 = Buffer.alloc(t6 + padLen + 1 + b6);
353
+ this.tweak.copy(Q2, 0);
354
+ Q2[t6 + padLen] = i6;
355
+ this._bigintToBuffer(this._strToInt(B3), b6).copy(Q2, t6 + padLen + 1);
356
+ const R2 = this._prf(Buffer.concat([P2, Q2]));
357
+ let S2 = Buffer.from(R2);
358
+ let j6 = 1;
359
+ while (S2.length < d6) {
360
+ const xorBlock = Buffer.alloc(16);
361
+ const jBuf = Buffer.alloc(16);
362
+ jBuf.writeUInt32BE(j6, 12);
363
+ for (let k6 = 0; k6 < 16; k6++) xorBlock[k6] = R2[k6] ^ jBuf[k6];
364
+ S2 = Buffer.concat([S2, this._ciph(xorBlock)]);
365
+ j6++;
366
+ }
367
+ S2 = S2.subarray(0, d6);
368
+ let y3 = 0n;
369
+ for (let k6 = 0; k6 < S2.length; k6++) {
370
+ y3 = (y3 << 8n) + BigInt(S2[k6]);
371
+ }
372
+ const modulo = BigInt(this.radix) ** BigInt(m6);
373
+ const c6 = (this._strToInt(A3) + y3) % modulo;
374
+ const C3 = this._intToStr(c6, m6);
375
+ A3 = B3;
376
+ B3 = C3;
377
+ }
378
+ return A3 + B3;
379
+ }
380
+ decrypt(X) {
381
+ const n6 = X.length;
382
+ const t6 = this.tweak.length;
383
+ if (n6 < 2) return X;
384
+ const u6 = Math.floor(n6 / 2);
385
+ const v7 = n6 - u6;
386
+ let A3 = X.substring(0, u6);
387
+ let B3 = X.substring(u6);
388
+ if (n6 % 2 !== 0) {
389
+ const temp = A3;
390
+ A3 = B3;
391
+ B3 = temp;
392
+ }
393
+ const b6 = Math.ceil(Math.ceil(v7 * Math.log2(this.radix)) / 8);
394
+ const d6 = 4 * Math.ceil(b6 / 4) + 4;
395
+ const P2 = Buffer.alloc(16);
396
+ P2[0] = 1;
397
+ P2[1] = 2;
398
+ P2[2] = 1;
399
+ P2.writeUIntBE(this.radix, 3, 3);
400
+ P2[6] = 10;
401
+ P2[7] = u6 % 256;
402
+ P2.writeUInt32BE(n6, 8);
403
+ P2.writeUInt32BE(t6, 12);
404
+ for (let i6 = 9; i6 >= 0; i6--) {
405
+ const m6 = i6 % 2 === 0 ? u6 : v7;
406
+ const padLen = ((-t6 - b6 - 1) % 16 + 16) % 16;
407
+ const Q2 = Buffer.alloc(t6 + padLen + 1 + b6);
408
+ this.tweak.copy(Q2, 0);
409
+ Q2[t6 + padLen] = i6;
410
+ this._bigintToBuffer(this._strToInt(A3), b6).copy(Q2, t6 + padLen + 1);
411
+ const R2 = this._prf(Buffer.concat([P2, Q2]));
412
+ let S2 = Buffer.from(R2);
413
+ let j6 = 1;
414
+ while (S2.length < d6) {
415
+ const xorBlock = Buffer.alloc(16);
416
+ const jBuf = Buffer.alloc(16);
417
+ jBuf.writeUInt32BE(j6, 12);
418
+ for (let k6 = 0; k6 < 16; k6++) xorBlock[k6] = R2[k6] ^ jBuf[k6];
419
+ S2 = Buffer.concat([S2, this._ciph(xorBlock)]);
420
+ j6++;
421
+ }
422
+ S2 = S2.subarray(0, d6);
423
+ let y3 = 0n;
424
+ for (let k6 = 0; k6 < S2.length; k6++) {
425
+ y3 = (y3 << 8n) + BigInt(S2[k6]);
426
+ }
427
+ const modulo = BigInt(this.radix) ** BigInt(m6);
428
+ let c6 = (this._strToInt(B3) - y3) % modulo;
429
+ if (c6 < 0n) c6 += modulo;
430
+ const C3 = this._intToStr(c6, m6);
431
+ B3 = A3;
432
+ A3 = C3;
433
+ }
434
+ if (n6 % 2 !== 0) {
435
+ const temp = A3;
436
+ A3 = B3;
437
+ B3 = temp;
438
+ }
439
+ return A3 + B3;
440
+ }
441
+ };
237
442
  }
238
443
  });
239
444
 
@@ -460,13 +665,13 @@ function looksLikeToken(value) {
460
665
  if (/^\+[1-9]\d{0,3}-555-\d{7}$/.test(v7)) {
461
666
  return true;
462
667
  }
463
- if (v7.startsWith("000-00-") && v7.length === 11) {
668
+ if (/^\d{3}-\d{2}-\d{4}$/.test(v7)) {
464
669
  return true;
465
670
  }
466
- if (v7.startsWith("4000-0000-0000-") && v7.length === 19) {
671
+ if (/^\d{4}-\d{4}-\d{4}-\d{4}$/.test(v7)) {
467
672
  return true;
468
673
  }
469
- if (v7.startsWith("000000") && v7.length === 9) {
674
+ if (v7.length === 9 && /^\d+$/.test(v7)) {
470
675
  return true;
471
676
  }
472
677
  if (v7.length === 9 && v7.startsWith("000") && /[A-Z]$/.test(v7)) {
@@ -490,11 +695,28 @@ function looksLikeToken(value) {
490
695
  }
491
696
  return false;
492
697
  }
698
+ function isUnambiguouslySafeToken(value) {
699
+ if (typeof value !== "string") return false;
700
+ const v7 = value.trim();
701
+ if (v7.startsWith("tkn-") && v7.includes("@")) {
702
+ const parts = v7.split("@");
703
+ if (parts.length === 2 && parts[0].length >= 12 && parts[1].includes(".")) {
704
+ return true;
705
+ }
706
+ }
707
+ if (/^\+[1-9]\d{0,3}-555-\d{7}$/.test(v7)) return true;
708
+ if (/^000\d{5}[A-Z]$/.test(v7)) return true;
709
+ if (/^[A-Z]{2}00[A-F0-9]{4,16}$/.test(v7)) return true;
710
+ if (/^<(PER|LOC|ORG):[^>]+>$/.test(v7)) return true;
711
+ if (v7.startsWith("[TKN-") && v7.endsWith("]")) return true;
712
+ if (/^[A-Z][a-zA-Z, ]+-[0-9]{3,4}$/.test(v7)) return true;
713
+ return false;
714
+ }
493
715
  var TOKEN_PATTERN;
494
716
  var init_fpe_utils = __esm({
495
717
  "src/core/fpe_utils.ts"() {
496
718
  TOKEN_PATTERN = new RegExp(
497
- "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}\\\\]",
719
+ "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-[^\\]]+\\]",
498
720
  // Opaque
499
721
  "g"
500
722
  );
@@ -509,12 +731,10 @@ async function _getMasterKey() {
509
731
  }
510
732
  if (!raw) {
511
733
  if (config.MASK_DEV_MODE) {
512
- raw = crypto2.randomBytes(32).toString("hex");
734
+ raw = cryptoNode.randomBytes(32).toString("hex");
513
735
  process.env.MASK_MASTER_KEY = raw;
514
736
  } else {
515
- throw new MaskSecurityError(
516
- "MASK_MASTER_KEY not set. Set it or use MASK_DEV_MODE=true for dev."
517
- );
737
+ throw new MaskSecurityError("MASK_MASTER_KEY not set.");
518
738
  }
519
739
  }
520
740
  _masterKey = Buffer.from(raw, "utf-8");
@@ -524,38 +744,34 @@ async function _getMasterKey() {
524
744
  function resetMasterKey() {
525
745
  _masterKey = null;
526
746
  }
527
- async function _hmacHex(plaintext, n6 = 8) {
747
+ async function _getAesKey() {
528
748
  const masterKey = await _getMasterKey();
529
- const digest = crypto2.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest("hex");
530
- return digest.slice(0, n6);
749
+ return cryptoNode.createHmac("sha256", masterKey).update(config.MASK_TENANT_ID, "utf-8").digest();
531
750
  }
532
- async function _hmacInt(plaintext) {
751
+ async function _hmacHex(plaintext, n6 = 8) {
533
752
  const masterKey = await _getMasterKey();
534
- const raw = crypto2.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest();
535
- let result = 0n;
536
- for (let i6 = 0; i6 < 16; i6++) {
537
- result = result << 8n | BigInt(raw[i6]);
538
- }
539
- return result;
540
- }
541
- async function _hmacDigits(plaintext, n6, offset = 0) {
542
- const salted = offset ? `${plaintext}::${offset}` : plaintext;
543
- const seed = await _hmacInt(salted);
544
- const modulus = 10n ** BigInt(n6);
545
- return (seed % modulus).toString().padStart(n6, "0");
753
+ const digest = cryptoNode.createHmac("sha256", masterKey).update(plaintext, "utf-8").digest("hex");
754
+ return digest.slice(0, n6);
546
755
  }
547
756
  async function _getBijectiveTweak() {
548
- const masterKey = await _getMasterKey();
549
- let base = config.MASK_TENANT_ID;
550
757
  if (config.MASK_SALT_ROTATION !== "NONE") {
551
- const now = /* @__PURE__ */ new Date();
552
- if (config.MASK_SALT_ROTATION === "MONTHLY") {
553
- base += `-${now.getUTCFullYear()}-${now.getUTCMonth() + 1}`;
554
- } else if (config.MASK_SALT_ROTATION === "YEARLY") {
555
- base += `-${now.getUTCFullYear()}`;
556
- }
758
+ console.warn(
759
+ `[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.`
760
+ );
557
761
  }
558
- return crypto2.createHmac("sha256", masterKey).update(base, "utf-8").digest();
762
+ const masterKey = await _getMasterKey();
763
+ return cryptoNode.createHmac("sha256", masterKey).update(config.MASK_TENANT_ID, "utf-8").digest();
764
+ }
765
+ async function _encryptBijectiveFF1(text) {
766
+ const canonical = text.toLowerCase().trim();
767
+ const hash = cryptoNode.createHash("sha256").update(canonical, "utf-8").digest();
768
+ const inputInt = hash.readBigUInt64BE(0);
769
+ const inputStr = inputInt.toString().padStart(20, "0");
770
+ const aesKey = await _getAesKey();
771
+ const tweak = await _getBijectiveTweak();
772
+ const engine = new FF1(aesKey, tweak, 10);
773
+ const cipherStr = engine.encrypt(inputStr);
774
+ return BigInt(cipherStr) % 2n ** 64n;
559
775
  }
560
776
  function _renderBijectivePerson(bits) {
561
777
  const firstIdx = Number(bits & 0x7FFn);
@@ -603,7 +819,10 @@ function _computeLuhnDigit(partialNum) {
603
819
  function _computeEsIdCheck(num) {
604
820
  return "TRWAGMYFPDXBNJZSQVHLCKE"[num % 23];
605
821
  }
606
- async function generateFPEToken(rawText, entityType = "UNKNOWN") {
822
+ function _stripCcSeparators(text) {
823
+ return text.replace(/[\s\-]/g, "");
824
+ }
825
+ async function generateDPToken(rawText, entityType = "UNKNOWN") {
607
826
  const text = rawText.trim();
608
827
  let type = (entityType || "UNKNOWN").toUpperCase();
609
828
  if (type === "UNKNOWN") {
@@ -623,49 +842,73 @@ async function generateFPEToken(rawText, entityType = "UNKNOWN") {
623
842
  if (type === "PHONE_NUMBER" || type === "PHONE_NUM" || type === "PHONE_NUM_INTL") {
624
843
  const m6 = text.match(/^\+([1-9]\d{0,3})/);
625
844
  const cc = m6 ? m6[1] : "1";
626
- return `+${cc}-555-${await _hmacDigits(text, 7)}`;
845
+ const digits = text.replace(/\D/g, "");
846
+ if (digits.length >= 7) {
847
+ const last7 = digits.slice(-7);
848
+ const engine = new FF1(await _getAesKey(), Buffer.from("PHONE"), 10);
849
+ const enc = engine.encrypt(last7);
850
+ return `+${cc}-555-${enc}`;
851
+ }
627
852
  }
628
853
  if (type === "US_SSN") {
629
- return `000-00-${await _hmacDigits(text, 4)}`;
854
+ const digits = text.replace(/-/g, "");
855
+ if (digits.length === 9) {
856
+ const engine = new FF1(await _getAesKey(), Buffer.from("US_SSN"), 10);
857
+ const enc = engine.encrypt(digits);
858
+ return `${enc.slice(0, 3)}-${enc.slice(3, 5)}-${enc.slice(5, 9)}`;
859
+ }
630
860
  }
631
861
  if (type === "CREDIT_CARD" || type === "CREDIT_CARD_NUMBER") {
632
- const base = `400000000000${await _hmacDigits(text, 3)}`;
633
- const checkDig = _computeLuhnDigit(base);
634
- const full = base + checkDig;
635
- return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
862
+ const digits = _stripCcSeparators(text);
863
+ if (digits.length === 16) {
864
+ const bin6 = digits.slice(0, 6);
865
+ const last4 = digits.slice(12, 16);
866
+ const middle6 = digits.slice(6, 12);
867
+ const engine = new FF1(await _getAesKey(), Buffer.from("CREDIT_CARD"), 10);
868
+ const encMiddle = engine.encrypt(middle6);
869
+ const base15 = bin6 + encMiddle + last4.slice(0, 3);
870
+ const checkDig = _computeLuhnDigit(base15);
871
+ const full = bin6 + encMiddle + last4.slice(0, 3) + checkDig;
872
+ return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
873
+ } else {
874
+ const fallbackDigits = digits.padEnd(16, "0").slice(0, 16);
875
+ const engine = new FF1(await _getAesKey(), Buffer.from("CREDIT_CARD"), 10);
876
+ const encMiddle = engine.encrypt(fallbackDigits.slice(6, 12));
877
+ const full = fallbackDigits.slice(0, 6) + encMiddle + fallbackDigits.slice(12);
878
+ return `${full.slice(0, 4)}-${full.slice(4, 8)}-${full.slice(8, 12)}-${full.slice(12, 16)}`;
879
+ }
636
880
  }
637
881
  if (type === "US_ROUTING_NUMBER" || type === "US_ABA_ROUTING") {
638
- return `000000${await _hmacDigits(text, 3)}`;
882
+ if (text.length === 9 && /^\d+$/.test(text)) {
883
+ const engine = new FF1(await _getAesKey(), Buffer.from("US_ROUTING"), 10);
884
+ return engine.encrypt(text);
885
+ }
639
886
  }
640
887
  if (type === "INTL_BANK_IBAN" || type === "IBAN_CODE") {
641
888
  const countryCode = text.length >= 2 && /[a-zA-Z]{2}/.test(text.slice(0, 2)) ? text.slice(0, 2).toUpperCase() : "US";
642
889
  return `${countryCode}00${(await _hmacHex(text, 8)).toUpperCase()}`;
643
890
  }
644
891
  if (type === "ES_ID" || type === "ES_DNI") {
645
- const digits = `000${await _hmacDigits(text, 5)}`;
646
- return digits + _computeEsIdCheck(parseInt(digits, 10));
892
+ let digits = text.toUpperCase().replace(/[A-Z]/g, "");
893
+ if (digits) {
894
+ digits = digits.padStart(8, "0");
895
+ const engine = new FF1(await _getAesKey(), Buffer.from("ES_ID"), 10);
896
+ const enc = engine.encrypt(digits.slice(-5));
897
+ const tokenDigits = `000${enc}`;
898
+ return tokenDigits + _computeEsIdCheck(parseInt(tokenDigits, 10));
899
+ }
647
900
  }
648
901
  if (type === "PERSON" || type === "PERSON_NAME") {
649
902
  if (config.MASK_BIJECTIVE_MODE) {
650
- const canonical = text.toLowerCase().trim();
651
- const hash = crypto2.createHash("sha256").update(canonical, "utf-8").digest();
652
- const inputInt = hash.readBigUInt64BE(0);
653
- const masterKey = await _getMasterKey();
654
- const engine = new FF1(masterKey.slice(0, 16), await _getBijectiveTweak());
655
- const cipher = engine.encrypt(inputInt);
656
- return _renderBijectivePerson(cipher);
903
+ const cipherBits = await _encryptBijectiveFF1(text);
904
+ return _renderBijectivePerson(cipherBits);
657
905
  }
658
906
  return `[TKN-PERSON-${await _hmacHex(text)}]`;
659
907
  }
660
908
  if (type === "LOCATION" || type === "PHYS_ADDRESS") {
661
909
  if (config.MASK_BIJECTIVE_MODE) {
662
- const canonical = text.toLowerCase().trim();
663
- const hash = crypto2.createHash("sha256").update(canonical, "utf-8").digest();
664
- const inputInt = hash.readBigUInt64BE(0);
665
- const masterKey = await _getMasterKey();
666
- const engine = new FF1(masterKey.slice(0, 16), await _getBijectiveTweak());
667
- const cipher = engine.encrypt(inputInt);
668
- return _renderBijectiveLocation(cipher);
910
+ const cipherBits = await _encryptBijectiveFF1(text);
911
+ return _renderBijectiveLocation(cipherBits);
669
912
  }
670
913
  return `[TKN-LOC-${await _hmacHex(text)}]`;
671
914
  }
@@ -674,12 +917,13 @@ async function generateFPEToken(rawText, entityType = "UNKNOWN") {
674
917
  }
675
918
  return `[TKN-${await _hmacHex(text)}]`;
676
919
  }
677
- var _masterKey, _EMAIL_RE, _PHONE_RE, _SSN_RE, _CC_RE, _ROUTING_RE, _ES_ID_RE, _IBAN_RE, FF1;
920
+ var _masterKey, _EMAIL_RE, _PHONE_RE, _SSN_RE, _CC_RE, _ROUTING_RE, _ES_ID_RE, _IBAN_RE, generateFPEToken;
678
921
  var init_fpe = __esm({
679
922
  "src/core/fpe.ts"() {
680
923
  init_config();
681
924
  init_key_provider();
682
925
  init_exceptions();
926
+ init_ff1();
683
927
  init_synthesisLibrary();
684
928
  init_fpe_utils();
685
929
  _masterKey = null;
@@ -690,49 +934,7 @@ var init_fpe = __esm({
690
934
  _ROUTING_RE = /^\d{9}$/;
691
935
  _ES_ID_RE = /^(?:\d{8}[A-Z]|[XYZ]\d{7}[A-Z])$/;
692
936
  _IBAN_RE = /^[A-Z]{2}\d{2}[A-Z0-9]{4,30}$/;
693
- FF1 = class {
694
- /** NIST SP 800-38G FF1 implementation (simplified for 64-bit domains). */
695
- constructor(key, tweak) {
696
- this.key = key;
697
- this.tweak = tweak;
698
- }
699
- encrypt(n6) {
700
- let A3 = n6 >> 32n;
701
- let B3 = n6 & 0xFFFFFFFFn;
702
- const radix = 2n ** 32n;
703
- for (let i6 = 0; i6 < 10; i6++) {
704
- const tweakInfoBuffer = Buffer.alloc(8);
705
- tweakInfoBuffer.writeUInt32BE(i6, 0);
706
- tweakInfoBuffer.writeUInt32BE(Number(B3), 4);
707
- const tweakInfoCombined = Buffer.concat([this.tweak, tweakInfoBuffer]);
708
- const h6 = crypto2.createHmac("sha256", this.key).update(tweakInfoCombined).digest();
709
- const roundVal = BigInt(h6.readUInt32BE(0));
710
- const Anext = B3;
711
- const Bnext = (A3 + roundVal) % radix;
712
- A3 = Anext;
713
- B3 = Bnext;
714
- }
715
- return A3 << 32n | B3;
716
- }
717
- decrypt(n6) {
718
- let A3 = n6 >> 32n;
719
- let B3 = n6 & 0xFFFFFFFFn;
720
- const radix = 2n ** 32n;
721
- for (let i6 = 9; i6 >= 0; i6--) {
722
- const tweakInfoBuffer = Buffer.alloc(8);
723
- tweakInfoBuffer.writeUInt32BE(i6, 0);
724
- tweakInfoBuffer.writeUInt32BE(Number(A3), 4);
725
- const tweakInfoCombined = Buffer.concat([this.tweak, tweakInfoBuffer]);
726
- const h6 = crypto2.createHmac("sha256", this.key).update(tweakInfoCombined).digest();
727
- const roundVal = BigInt(h6.readUInt32BE(0));
728
- const Bprev = A3;
729
- const Aprev = (B3 - roundVal + radix) % radix;
730
- A3 = Aprev;
731
- B3 = Bprev;
732
- }
733
- return A3 << 32n | B3;
734
- }
735
- };
937
+ generateFPEToken = generateDPToken;
736
938
  }
737
939
  });
738
940
 
@@ -749,39 +951,39 @@ var require_core = __commonJS({
749
951
  }
750
952
  })(exports2, function() {
751
953
  var CryptoJS = CryptoJS || (function(Math2, undefined2) {
752
- var crypto6;
954
+ var crypto7;
753
955
  if (typeof window !== "undefined" && window.crypto) {
754
- crypto6 = window.crypto;
956
+ crypto7 = window.crypto;
755
957
  }
756
958
  if (typeof self !== "undefined" && self.crypto) {
757
- crypto6 = self.crypto;
959
+ crypto7 = self.crypto;
758
960
  }
759
961
  if (typeof globalThis !== "undefined" && globalThis.crypto) {
760
- crypto6 = globalThis.crypto;
962
+ crypto7 = globalThis.crypto;
761
963
  }
762
- if (!crypto6 && typeof window !== "undefined" && window.msCrypto) {
763
- crypto6 = window.msCrypto;
964
+ if (!crypto7 && typeof window !== "undefined" && window.msCrypto) {
965
+ crypto7 = window.msCrypto;
764
966
  }
765
- if (!crypto6 && typeof global !== "undefined" && global.crypto) {
766
- crypto6 = global.crypto;
967
+ if (!crypto7 && typeof global !== "undefined" && global.crypto) {
968
+ crypto7 = global.crypto;
767
969
  }
768
- if (!crypto6 && typeof __require === "function") {
970
+ if (!crypto7 && typeof __require === "function") {
769
971
  try {
770
- crypto6 = __require("crypto");
972
+ crypto7 = __require("crypto");
771
973
  } catch (err2) {
772
974
  }
773
975
  }
774
976
  var cryptoSecureRandomInt = function() {
775
- if (crypto6) {
776
- if (typeof crypto6.getRandomValues === "function") {
977
+ if (crypto7) {
978
+ if (typeof crypto7.getRandomValues === "function") {
777
979
  try {
778
- return crypto6.getRandomValues(new Uint32Array(1))[0];
980
+ return crypto7.getRandomValues(new Uint32Array(1))[0];
779
981
  } catch (err2) {
780
982
  }
781
983
  }
782
- if (typeof crypto6.randomBytes === "function") {
984
+ if (typeof crypto7.randomBytes === "function") {
783
985
  try {
784
- return crypto6.randomBytes(4).readInt32LE();
986
+ return crypto7.randomBytes(4).readInt32LE();
785
987
  } catch (err2) {
786
988
  }
787
989
  }
@@ -3047,7 +3249,7 @@ var require_fernet = __commonJS({
3047
3249
  var Base64 = require_enc_base64();
3048
3250
  var HmacSHA256 = require_hmac_sha256();
3049
3251
  var URLBase64 = require_urlsafe_base642();
3050
- var crypto6 = __require("crypto");
3252
+ var crypto7 = __require("crypto");
3051
3253
  String.prototype.lpad = function(padString, length) {
3052
3254
  var str = this;
3053
3255
  while (str.length < length) str = padString + str;
@@ -3074,7 +3276,7 @@ var require_fernet = __commonJS({
3074
3276
  return hex;
3075
3277
  };
3076
3278
  var randomHex = function(size) {
3077
- return crypto6.randomBytes(128 / 8).toString("hex");
3279
+ return crypto7.randomBytes(128 / 8).toString("hex");
3078
3280
  };
3079
3281
  var setIV = function setIV2(iv_array) {
3080
3282
  if (iv_array) {
@@ -3157,24 +3359,28 @@ function getCryptoEngine() {
3157
3359
  async function getCryptoEngineAsync() {
3158
3360
  return await CryptoEngine.getInstanceAsync();
3159
3361
  }
3160
- var GCM_IV_BYTES, GCM_AUTH_TAG_BYTES, GCM_ALGORITHM, AES_GCM_PREFIX, _CryptoEngine, CryptoEngine;
3362
+ 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;
3161
3363
  var init_crypto = __esm({
3162
3364
  "src/core/crypto.ts"() {
3163
3365
  init_config();
3164
3366
  init_key_provider();
3165
3367
  init_exceptions();
3368
+ AES_KEY_BYTES = 32;
3166
3369
  GCM_IV_BYTES = 12;
3167
3370
  GCM_AUTH_TAG_BYTES = 16;
3168
3371
  GCM_ALGORITHM = "aes-256-gcm";
3169
- AES_GCM_PREFIX = "aes:";
3372
+ AES_V2_PREFIX = "aes:v2:";
3373
+ AES_GCM_PREFIX = "aes:v1:";
3374
+ AES_GCM_LEGACY_PREFIX = "aes:";
3170
3375
  _CryptoEngine = class _CryptoEngine {
3171
3376
  constructor() {
3172
- this._aesKey = null;
3377
+ this._keyring = /* @__PURE__ */ new Map();
3378
+ this._activeKeyId = "default";
3173
3379
  this._indexSecret = null;
3174
3380
  }
3175
- /**
3381
+ /**
3176
3382
  * Return the singleton instance, initialising it if necessary.
3177
- * This is asynchronous because key providers (KMS, etc.) might be async.
3383
+ * Async because Argon2id key derivation is async.
3178
3384
  */
3179
3385
  static async getInstanceAsync() {
3180
3386
  if (this._instance === null) {
@@ -3190,33 +3396,96 @@ var init_crypto = __esm({
3190
3396
  }
3191
3397
  return this._instance;
3192
3398
  }
3193
- /** Clear the singleton instance to force re-initialization (useful for key rotation). */
3399
+ /** Clear the singleton (useful for key rotation / tests). */
3194
3400
  static reset() {
3195
3401
  this._instance = null;
3196
3402
  }
3403
+ async _deriveAesKey(rawKey, keyId) {
3404
+ let argon2;
3405
+ try {
3406
+ argon2 = __require("argon2");
3407
+ } catch (e6) {
3408
+ throw new Error(
3409
+ "The 'argon2' package is required for Mask SDK cryptographic operations. Install with: npm install argon2"
3410
+ );
3411
+ }
3412
+ const kdfSaltStr = config.MASK_KDF_SALT + "-" + config.MASK_TENANT_ID + "-" + keyId;
3413
+ const kdfSaltBytes = cryptoNode.createHash("sha256").update(kdfSaltStr).digest().subarray(0, 16);
3414
+ return await argon2.hash(rawKey, {
3415
+ type: argon2.argon2id,
3416
+ memoryCost: 19456,
3417
+ timeCost: 2,
3418
+ parallelism: 1,
3419
+ hashLength: AES_KEY_BYTES,
3420
+ salt: kdfSaltBytes,
3421
+ raw: true
3422
+ });
3423
+ }
3197
3424
  async _init() {
3425
+ let argon2;
3426
+ try {
3427
+ argon2 = __require("argon2");
3428
+ } catch (e6) {
3429
+ throw new Error("The 'argon2' package is required. Install with: npm install argon2");
3430
+ }
3198
3431
  const provider = getKeyProvider();
3199
- const keyFromProvider = await provider.getEncryptionKey();
3200
- let key;
3201
- if (!keyFromProvider) {
3202
- if (config.MASK_DEV_MODE) {
3203
- key = crypto2.randomBytes(32).toString("base64");
3204
- process.env.MASK_ENCRYPTION_KEY = key;
3205
- console.warn(
3206
- "MASK_DEV_MODE is enabled. Using a generated throwaway key. DO NOT USE THIS IN PRODUCTION \u2014 tokens will be lost on restart."
3207
- );
3208
- } else {
3209
- throw new Error(
3210
- "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."
3211
- );
3432
+ const rawKeys = /* @__PURE__ */ new Map();
3433
+ let activeKeyId = "default";
3434
+ const keyringJson = await provider.getKeyring();
3435
+ if (keyringJson) {
3436
+ let parsed;
3437
+ try {
3438
+ parsed = JSON.parse(keyringJson);
3439
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3440
+ throw new Error("MASK_KEYRING must be a non-empty JSON object.");
3441
+ }
3442
+ } catch (e6) {
3443
+ throw new Error(`Invalid MASK_KEYRING format: ${e6}`);
3212
3444
  }
3445
+ const entries = Object.entries(parsed);
3446
+ if (entries.length === 0) throw new Error("MASK_KEYRING must contain at least one key.");
3447
+ for (const [kid, k6] of entries) rawKeys.set(kid, k6);
3448
+ activeKeyId = entries[entries.length - 1][0];
3213
3449
  } else {
3214
- key = keyFromProvider;
3215
- }
3216
- this._aesKey = crypto2.createHash("sha256").update(key).digest();
3217
- const masterKey = await provider.getMasterKey() || key;
3218
- const salt = config.MASK_BLIND_INDEX_SALT;
3219
- this._indexSecret = crypto2.createHmac("sha256", masterKey).update(salt).digest();
3450
+ const keyFromProvider = await provider.getEncryptionKey();
3451
+ let key;
3452
+ if (!keyFromProvider) {
3453
+ if (config.MASK_DEV_MODE) {
3454
+ key = cryptoNode.randomBytes(32).toString("base64");
3455
+ process.env.MASK_ENCRYPTION_KEY = key;
3456
+ console.warn(
3457
+ "MASK_DEV_MODE is enabled. Using a generated throwaway key. DO NOT USE THIS IN PRODUCTION \u2014 tokens will be lost on restart."
3458
+ );
3459
+ } else {
3460
+ throw new Error(
3461
+ "MASK_ENCRYPTION_KEY or MASK_KEYRING is not set. Set one of these, or set MASK_DEV_MODE=true for ephemeral use."
3462
+ );
3463
+ }
3464
+ } else {
3465
+ key = keyFromProvider;
3466
+ }
3467
+ rawKeys.set("default", key);
3468
+ activeKeyId = "default";
3469
+ }
3470
+ this._keyring = /* @__PURE__ */ new Map();
3471
+ for (const [kid, rawKey] of rawKeys) {
3472
+ this._keyring.set(kid, await this._deriveAesKey(rawKey, kid));
3473
+ }
3474
+ this._activeKeyId = activeKeyId;
3475
+ const rawKeysArr = Array.from(rawKeys.values());
3476
+ const lastRawKey = rawKeysArr[rawKeysArr.length - 1];
3477
+ const masterKey = await provider.getMasterKey() || lastRawKey;
3478
+ const indexSaltStr = config.MASK_BLIND_INDEX_SALT + "-" + config.MASK_TENANT_ID;
3479
+ const indexSaltBytes = cryptoNode.createHash("sha256").update(indexSaltStr).digest().subarray(0, 16);
3480
+ this._indexSecret = await argon2.hash(masterKey, {
3481
+ type: argon2.argon2id,
3482
+ memoryCost: 19456,
3483
+ timeCost: 2,
3484
+ parallelism: 1,
3485
+ hashLength: AES_KEY_BYTES,
3486
+ salt: indexSaltBytes,
3487
+ raw: true
3488
+ });
3220
3489
  }
3221
3490
  /** Return the secret used for HMAC-based blind indexing. */
3222
3491
  async getIndexSecret() {
@@ -3225,36 +3494,56 @@ var init_crypto = __esm({
3225
3494
  }
3226
3495
  return this._indexSecret;
3227
3496
  }
3497
+ /** Encrypt plaintext using the active keyring key.
3498
+ * Envelope format: aes:v2:{keyId}:{base64(iv+authTag+ciphertext)}
3499
+ */
3228
3500
  encrypt(plaintext) {
3229
- if (!this._aesKey) {
3230
- throw new Error("CryptoEngine not initialised. AES key missing.");
3231
- }
3232
- const iv = crypto2.randomBytes(GCM_IV_BYTES);
3233
- const cipher = crypto2.createCipheriv(GCM_ALGORITHM, this._aesKey, iv);
3234
- const encrypted = Buffer.concat([
3235
- cipher.update(plaintext, "utf8"),
3236
- cipher.final()
3237
- ]);
3501
+ const aesKey = this._keyring.get(this._activeKeyId);
3502
+ if (!aesKey) {
3503
+ throw new Error(`CryptoEngine: active key ID '${this._activeKeyId}' not found in keyring.`);
3504
+ }
3505
+ const iv = cryptoNode.randomBytes(GCM_IV_BYTES);
3506
+ const cipher = cryptoNode.createCipheriv(GCM_ALGORITHM, aesKey, iv);
3507
+ const plaintextBuf = Buffer.from(plaintext, "utf8");
3508
+ const encrypted = Buffer.concat([cipher.update(plaintextBuf), cipher.final()]);
3238
3509
  const authTag = cipher.getAuthTag();
3510
+ plaintextBuf.fill(0);
3239
3511
  const combined = Buffer.concat([iv, authTag, encrypted]);
3240
- return AES_GCM_PREFIX + combined.toString("base64");
3512
+ return `${AES_V2_PREFIX}${this._activeKeyId}:${combined.toString("base64")}`;
3241
3513
  }
3514
+ /** Decrypt ciphertext. Supports all historical envelope formats. */
3242
3515
  decrypt(ciphertext) {
3243
- if (!this._aesKey) {
3244
- throw new Error("CryptoEngine not initialised. AES key missing.");
3516
+ if (this._keyring.size === 0) {
3517
+ throw new Error("CryptoEngine not initialised.");
3245
3518
  }
3246
3519
  try {
3520
+ if (ciphertext.startsWith(AES_V2_PREFIX)) {
3521
+ const rest = ciphertext.slice(AES_V2_PREFIX.length);
3522
+ const sep3 = rest.indexOf(":");
3523
+ if (sep3 === -1) throw new Error("Malformed aes:v2 envelope: missing key ID separator.");
3524
+ const keyId = rest.slice(0, sep3);
3525
+ const b64 = rest.slice(sep3 + 1);
3526
+ return this._decryptAesGcm(keyId, b64);
3527
+ }
3247
3528
  if (ciphertext.startsWith(AES_GCM_PREFIX)) {
3248
- return this._decryptAesGcm(ciphertext.slice(AES_GCM_PREFIX.length));
3529
+ return this._decryptAesGcm("default", ciphertext.slice(AES_GCM_PREFIX.length));
3530
+ }
3531
+ if (ciphertext.startsWith(AES_GCM_LEGACY_PREFIX)) {
3532
+ return this._decryptAesGcm("default", ciphertext.slice(AES_GCM_LEGACY_PREFIX.length));
3249
3533
  }
3250
3534
  return this._decryptLegacyFernet(ciphertext);
3251
3535
  } catch (e6) {
3252
- console.error("Failed to decrypt vault payload. Check your MASK_ENCRYPTION_KEY. Inner error:", e6);
3536
+ console.error("Failed to decrypt vault payload. Check your MASK_ENCRYPTION_KEY / MASK_KEYRING. Inner error:", e6);
3253
3537
  throw new MaskDecryptionError("Decryption failed");
3254
3538
  }
3255
3539
  }
3256
- /** Decrypt an AES-256-GCM token (base64 encoded). */
3257
- _decryptAesGcm(b64) {
3540
+ _decryptAesGcm(keyId, b64) {
3541
+ const aesKey = this._keyring.get(keyId);
3542
+ if (!aesKey) {
3543
+ throw new MaskDecryptionError(
3544
+ `No key found for key ID '${keyId}'. Ensure the key is present in MASK_KEYRING.`
3545
+ );
3546
+ }
3258
3547
  const combined = Buffer.from(b64, "base64");
3259
3548
  if (combined.length < GCM_IV_BYTES + GCM_AUTH_TAG_BYTES) {
3260
3549
  throw new Error("Ciphertext too short for AES-GCM");
@@ -3262,22 +3551,11 @@ var init_crypto = __esm({
3262
3551
  const iv = combined.subarray(0, GCM_IV_BYTES);
3263
3552
  const authTag = combined.subarray(GCM_IV_BYTES, GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3264
3553
  const encrypted = combined.subarray(GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3265
- const decipher = crypto2.createDecipheriv(GCM_ALGORITHM, this._aesKey, iv);
3554
+ const decipher = cryptoNode.createDecipheriv(GCM_ALGORITHM, aesKey, iv);
3266
3555
  decipher.setAuthTag(authTag);
3267
- const decrypted = Buffer.concat([
3268
- decipher.update(encrypted),
3269
- decipher.final()
3270
- ]);
3556
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
3271
3557
  return decrypted.toString("utf8");
3272
3558
  }
3273
- /**
3274
- * Attempt to decrypt a legacy Fernet-format token.
3275
- *
3276
- * Fernet format: Version (1) || Timestamp (8) || IV (16) || Ciphertext (var) || HMAC (32)
3277
- * All base64url-encoded.
3278
- *
3279
- * We try to use the `fernet` npm package if available, otherwise throw.
3280
- */
3281
3559
  _decryptLegacyFernet(ciphertext) {
3282
3560
  let fernet;
3283
3561
  try {
@@ -3305,8 +3583,6 @@ var init_crypto = __esm({
3305
3583
  CryptoEngine = _CryptoEngine;
3306
3584
  }
3307
3585
  });
3308
-
3309
- // src/telemetry/audit_logger.ts
3310
3586
  function _getLogLevel() {
3311
3587
  const env3 = config.MASK_LOG_LEVEL;
3312
3588
  return env3 in LOG_LEVELS ? env3 : "info";
@@ -3340,16 +3616,15 @@ function getLogger(name) {
3340
3616
  error: (...args) => _log("error", ...args)
3341
3617
  };
3342
3618
  }
3343
- function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null) {
3619
+ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null, instanceId = "") {
3344
3620
  const event = {
3345
3621
  ts: Date.now() / 1e3,
3346
3622
  action,
3347
- // "encode" | "decode" | "expired" | "error"
3348
3623
  token,
3349
3624
  data_type: dataType,
3350
- // "email" | "phone" | "ssn" | "opaque"
3351
3625
  agent,
3352
- tool
3626
+ tool,
3627
+ instance_id: instanceId
3353
3628
  };
3354
3629
  if (extra) {
3355
3630
  Object.assign(event, _deepMask(extra));
@@ -3359,7 +3634,7 @@ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null
3359
3634
  function _deepMask(obj) {
3360
3635
  if (obj === null || obj === void 0) return obj;
3361
3636
  if (typeof obj === "string") {
3362
- return looksLikeToken(obj) ? obj : "[REDACTED]";
3637
+ return isUnambiguouslySafeToken(obj) ? obj : "[REDACTED]";
3363
3638
  }
3364
3639
  if (typeof obj !== "object") return obj;
3365
3640
  if (Array.isArray(obj)) {
@@ -3393,6 +3668,10 @@ var init_audit_logger = __esm({
3393
3668
  this._shutdownRegistered = false;
3394
3669
  this._maxBufferSize = config.MASK_AUDIT_MAX_BUFFER_SIZE;
3395
3670
  this._strictMode = config.MASK_AUDIT_LOG_STRICT;
3671
+ const rawKey = process.env.MASK_MASTER_KEY || process.env.MASK_ENCRYPTION_KEY || "";
3672
+ this._signingKey = cryptoNode.createHash("sha256").update(rawKey).digest();
3673
+ this._instanceId = cryptoNode.randomUUID();
3674
+ this._prevSig = cryptoNode.createHmac("sha256", this._signingKey).update(this._instanceId, "utf-8").digest("hex");
3396
3675
  }
3397
3676
  static getInstance() {
3398
3677
  if (this._instance === null) {
@@ -3400,16 +3679,46 @@ var init_audit_logger = __esm({
3400
3679
  }
3401
3680
  return this._instance;
3402
3681
  }
3682
+ _getOverflowPath() {
3683
+ const d6 = process.env.MASK_SECURE_AUDIT_LOG_DIR || __require("os").tmpdir();
3684
+ return path2.join(d6, `mask_audit_overflow_${this._instanceId}.ndjson`);
3685
+ }
3686
+ _writeOverflow(event) {
3687
+ try {
3688
+ fs.appendFileSync(this._getOverflowPath(), JSON.stringify(event) + "\n", "utf-8");
3689
+ } catch {
3690
+ }
3691
+ }
3692
+ _consumeOverflow(events) {
3693
+ const overflowPath = this._getOverflowPath();
3694
+ if (!fs.existsSync(overflowPath)) return;
3695
+ const processingPath = overflowPath + ".processing";
3696
+ try {
3697
+ fs.renameSync(overflowPath, processingPath);
3698
+ } catch {
3699
+ return;
3700
+ }
3701
+ try {
3702
+ const content = fs.readFileSync(processingPath, "utf-8");
3703
+ for (const line of content.split("\n")) {
3704
+ if (line.trim()) events.push(JSON.parse(line));
3705
+ }
3706
+ fs.unlinkSync(processingPath);
3707
+ } catch (e6) {
3708
+ _logger.error(`Failed to consume overflow: ${e6}`);
3709
+ }
3710
+ }
3403
3711
  log(action, token, dataType = "opaque", agent = "", tool = "", extra = {}) {
3404
- const event = _makeEvent(action, token, dataType, agent, tool, extra);
3712
+ const event = _makeEvent(action, token, dataType, agent, tool, extra, this._instanceId);
3405
3713
  if (this._buffer.length >= this._maxBufferSize) {
3406
3714
  if (!this._bufferFullWarned) {
3407
3715
  _logger.warn(
3408
- `AuditLogger buffer full (max=${this._maxBufferSize}). Performing emergency sync-flush to prevent data loss.`
3716
+ `AuditLogger buffer full (max=${this._maxBufferSize}). Spooling to disk overflow to prevent OOM.`
3409
3717
  );
3410
3718
  this._bufferFullWarned = true;
3411
3719
  }
3412
- this._flushSync();
3720
+ this._writeOverflow(event);
3721
+ return;
3413
3722
  }
3414
3723
  this._buffer.push(event);
3415
3724
  }
@@ -3439,27 +3748,84 @@ var init_audit_logger = __esm({
3439
3748
  await this._flush();
3440
3749
  }
3441
3750
  async _flush() {
3442
- if (this._isFlushing || this._buffer.length === 0) return;
3751
+ if (this._isFlushing) return;
3443
3752
  this._isFlushing = true;
3444
3753
  try {
3445
3754
  const events = [...this._buffer];
3446
3755
  this._buffer = [];
3447
3756
  this._bufferFullWarned = false;
3757
+ this._consumeOverflow(events);
3758
+ if (events.length === 0) return;
3759
+ const secureLogDir = process.env.MASK_SECURE_AUDIT_LOG_DIR || "";
3760
+ let secureStream = null;
3761
+ if (secureLogDir) {
3762
+ fs.mkdirSync(secureLogDir, { recursive: true });
3763
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3764
+ const filePath = path2.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3765
+ try {
3766
+ secureStream = fs.createWriteStream(filePath, { flags: "a" });
3767
+ } catch {
3768
+ }
3769
+ }
3448
3770
  for (const evt of events) {
3449
- const json = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3450
- console.info(json);
3771
+ const body = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3772
+ const sigInput = Buffer.from(this._prevSig + body, "utf-8");
3773
+ const sig = cryptoNode.createHmac("sha256", this._signingKey).update(sigInput).digest("hex");
3774
+ const signedLine = JSON.stringify({
3775
+ ...evt,
3776
+ prev_sig: this._prevSig,
3777
+ sig
3778
+ }, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3779
+ this._prevSig = sig;
3780
+ console.info(signedLine);
3781
+ if (secureStream) {
3782
+ secureStream.write(signedLine + "\n");
3783
+ }
3784
+ }
3785
+ if (secureStream) {
3786
+ secureStream.end();
3451
3787
  }
3452
3788
  } finally {
3453
3789
  this._isFlushing = false;
3454
3790
  }
3455
3791
  }
3456
- /** Synchronous flush for use in signal handlers where async is unreliable. */
3792
+ /** Synchronous flush for use in signal handlers where async is unreliable.
3793
+ *
3794
+ * Computes HMAC signatures to maintain chain integrity and writes to the
3795
+ * secure ndjson audit file (MASK_SECURE_AUDIT_LOG_DIR) if configured,
3796
+ * ensuring SOC 2 tamper-evidence guarantees hold through process shutdown.
3797
+ */
3457
3798
  _flushSync() {
3458
3799
  if (this._buffer.length === 0) return;
3459
3800
  const events = [...this._buffer];
3460
3801
  this._buffer = [];
3802
+ const secureLogDir = process.env.MASK_SECURE_AUDIT_LOG_DIR || "";
3803
+ let secureFilePath = null;
3804
+ if (secureLogDir) {
3805
+ try {
3806
+ fs.mkdirSync(secureLogDir, { recursive: true });
3807
+ const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3808
+ secureFilePath = path2.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3809
+ } catch {
3810
+ }
3811
+ }
3461
3812
  for (const evt of events) {
3462
- process.stdout.write(JSON.stringify(evt) + "\n");
3813
+ const body = JSON.stringify(evt, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3814
+ const sigInput = Buffer.from(this._prevSig + body, "utf-8");
3815
+ const sig = cryptoNode.createHmac("sha256", this._signingKey).update(sigInput).digest("hex");
3816
+ const signedLine = JSON.stringify({
3817
+ ...evt,
3818
+ prev_sig: this._prevSig,
3819
+ sig
3820
+ }, (_, v7) => typeof v7 === "bigint" ? v7.toString() : v7);
3821
+ this._prevSig = sig;
3822
+ process.stdout.write(signedLine + "\n");
3823
+ if (secureFilePath) {
3824
+ try {
3825
+ fs.appendFileSync(secureFilePath, signedLine + "\n", { encoding: "utf-8" });
3826
+ } catch {
3827
+ }
3828
+ }
3463
3829
  }
3464
3830
  }
3465
3831
  };
@@ -3511,7 +3877,7 @@ var init_search = __esm({
3511
3877
  static async getBucketIndex(bucketVal) {
3512
3878
  const engine = await getCryptoEngine();
3513
3879
  const secret = await engine.getIndexSecret();
3514
- return crypto2.createHmac("sha256", secret).update(bucketVal).digest("hex");
3880
+ return cryptoNode.createHmac("sha256", secret).update(bucketVal).digest("hex");
3515
3881
  }
3516
3882
  };
3517
3883
  }
@@ -15657,9 +16023,9 @@ var init_createPaginator = __esm({
15657
16023
  command = withCommand(command) ?? command;
15658
16024
  return await client.send(command, ...args);
15659
16025
  };
15660
- get = (fromObject, path3) => {
16026
+ get = (fromObject, path4) => {
15661
16027
  let cursor = fromObject;
15662
- const pathComponents = path3.split(".");
16028
+ const pathComponents = path4.split(".");
15663
16029
  for (const step of pathComponents) {
15664
16030
  if (!cursor || typeof cursor !== "object") {
15665
16031
  return void 0;
@@ -16270,12 +16636,12 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
16270
16636
  const password = request2.password ?? "";
16271
16637
  auth = `${username}:${password}`;
16272
16638
  }
16273
- let path3 = request2.path;
16639
+ let path4 = request2.path;
16274
16640
  if (queryString) {
16275
- path3 += `?${queryString}`;
16641
+ path4 += `?${queryString}`;
16276
16642
  }
16277
16643
  if (request2.fragment) {
16278
- path3 += `#${request2.fragment}`;
16644
+ path4 += `#${request2.fragment}`;
16279
16645
  }
16280
16646
  let hostname = request2.hostname ?? "";
16281
16647
  if (hostname[0] === "[" && hostname.endsWith("]")) {
@@ -16287,7 +16653,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
16287
16653
  headers: request2.headers,
16288
16654
  host: hostname,
16289
16655
  method: request2.method,
16290
- path: path3,
16656
+ path: path4,
16291
16657
  port: request2.port,
16292
16658
  agent,
16293
16659
  auth
@@ -16594,16 +16960,16 @@ var init_node_http2_handler = __esm({
16594
16960
  reject(err2);
16595
16961
  };
16596
16962
  const queryString = buildQueryString(query || {});
16597
- let path3 = request2.path;
16963
+ let path4 = request2.path;
16598
16964
  if (queryString) {
16599
- path3 += `?${queryString}`;
16965
+ path4 += `?${queryString}`;
16600
16966
  }
16601
16967
  if (request2.fragment) {
16602
- path3 += `#${request2.fragment}`;
16968
+ path4 += `#${request2.fragment}`;
16603
16969
  }
16604
16970
  const req = session.request({
16605
16971
  ...request2.headers,
16606
- [constants.HTTP2_HEADER_PATH]: path3,
16972
+ [constants.HTTP2_HEADER_PATH]: path4,
16607
16973
  [constants.HTTP2_HEADER_METHOD]: method
16608
16974
  });
16609
16975
  session.ref();
@@ -18052,10 +18418,10 @@ var init_date_utils = __esm({
18052
18418
  };
18053
18419
  }
18054
18420
  });
18055
- var randomUUID;
18421
+ var randomUUID2;
18056
18422
  var init_randomUUID = __esm({
18057
18423
  "node_modules/@smithy/uuid/dist-es/randomUUID.js"() {
18058
- randomUUID = crypto2__default.randomUUID.bind(crypto2__default);
18424
+ randomUUID2 = cryptoNode__default.randomUUID.bind(cryptoNode__default);
18059
18425
  }
18060
18426
  });
18061
18427
 
@@ -18066,8 +18432,8 @@ var init_v4 = __esm({
18066
18432
  init_randomUUID();
18067
18433
  decimalToHex = Array.from({ length: 256 }, (_, i6) => i6.toString(16).padStart(2, "0"));
18068
18434
  v4 = () => {
18069
- if (randomUUID) {
18070
- return randomUUID();
18435
+ if (randomUUID2) {
18436
+ return randomUUID2();
18071
18437
  }
18072
18438
  const rnds = new Uint8Array(16);
18073
18439
  crypto.getRandomValues(rnds);
@@ -18805,11 +19171,11 @@ var init_HttpBindingProtocol = __esm({
18805
19171
  const opTraits = translateTraits(operationSchema.traits);
18806
19172
  if (opTraits.http) {
18807
19173
  request2.method = opTraits.http[0];
18808
- const [path3, search] = opTraits.http[1].split("?");
19174
+ const [path4, search] = opTraits.http[1].split("?");
18809
19175
  if (request2.path == "/") {
18810
- request2.path = path3;
19176
+ request2.path = path4;
18811
19177
  } else {
18812
- request2.path += path3;
19178
+ request2.path += path4;
18813
19179
  }
18814
19180
  const traitSearchParams = new URLSearchParams(search ?? "");
18815
19181
  Object.assign(query, Object.fromEntries(traitSearchParams));
@@ -19842,18 +20208,18 @@ var getAttrPathList;
19842
20208
  var init_getAttrPathList = __esm({
19843
20209
  "node_modules/@smithy/util-endpoints/dist-es/lib/getAttrPathList.js"() {
19844
20210
  init_types2();
19845
- getAttrPathList = (path3) => {
19846
- const parts = path3.split(".");
20211
+ getAttrPathList = (path4) => {
20212
+ const parts = path4.split(".");
19847
20213
  const pathList = [];
19848
20214
  for (const part of parts) {
19849
20215
  const squareBracketIndex = part.indexOf("[");
19850
20216
  if (squareBracketIndex !== -1) {
19851
20217
  if (part.indexOf("]") !== part.length - 1) {
19852
- throw new EndpointError(`Path: '${path3}' does not end with ']'`);
20218
+ throw new EndpointError(`Path: '${path4}' does not end with ']'`);
19853
20219
  }
19854
20220
  const arrayIndex = part.slice(squareBracketIndex + 1, -1);
19855
20221
  if (Number.isNaN(parseInt(arrayIndex))) {
19856
- throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path3}'`);
20222
+ throw new EndpointError(`Invalid array index: '${arrayIndex}' in path: '${path4}'`);
19857
20223
  }
19858
20224
  if (squareBracketIndex !== 0) {
19859
20225
  pathList.push(part.slice(0, squareBracketIndex));
@@ -19874,9 +20240,9 @@ var init_getAttr = __esm({
19874
20240
  "node_modules/@smithy/util-endpoints/dist-es/lib/getAttr.js"() {
19875
20241
  init_types2();
19876
20242
  init_getAttrPathList();
19877
- getAttr = (value, path3) => getAttrPathList(path3).reduce((acc, index) => {
20243
+ getAttr = (value, path4) => getAttrPathList(path4).reduce((acc, index) => {
19878
20244
  if (typeof acc !== "object") {
19879
- throw new EndpointError(`Index '${index}' in '${path3}' not found in '${JSON.stringify(value)}'`);
20245
+ throw new EndpointError(`Index '${index}' in '${path4}' not found in '${JSON.stringify(value)}'`);
19880
20246
  } else if (Array.isArray(acc)) {
19881
20247
  return acc[parseInt(index)];
19882
20248
  }
@@ -19918,8 +20284,8 @@ var init_parseURL = __esm({
19918
20284
  return value;
19919
20285
  }
19920
20286
  if (typeof value === "object" && "hostname" in value) {
19921
- const { hostname: hostname2, port, protocol: protocol2 = "", path: path3 = "", query = {} } = value;
19922
- const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path3}`);
20287
+ const { hostname: hostname2, port, protocol: protocol2 = "", path: path4 = "", query = {} } = value;
20288
+ const url = new URL(`${protocol2}//${hostname2}${port ? `:${port}` : ""}${path4}`);
19923
20289
  url.search = Object.entries(query).map(([k6, v7]) => `${k6}=${v7}`).join("&");
19924
20290
  return url;
19925
20291
  }
@@ -21674,10 +22040,10 @@ ${longDate}
21674
22040
  ${credentialScope}
21675
22041
  ${toHex(hashedRequest)}`;
21676
22042
  }
21677
- getCanonicalPath({ path: path3 }) {
22043
+ getCanonicalPath({ path: path4 }) {
21678
22044
  if (this.uriEscapePath) {
21679
22045
  const normalizedPathSegments = [];
21680
- for (const pathSegment of path3.split("/")) {
22046
+ for (const pathSegment of path4.split("/")) {
21681
22047
  if (pathSegment?.length === 0)
21682
22048
  continue;
21683
22049
  if (pathSegment === ".")
@@ -21688,11 +22054,11 @@ ${toHex(hashedRequest)}`;
21688
22054
  normalizedPathSegments.push(pathSegment);
21689
22055
  }
21690
22056
  }
21691
- const normalizedPath = `${path3?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path3?.endsWith("/") ? "/" : ""}`;
22057
+ const normalizedPath = `${path4?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path4?.endsWith("/") ? "/" : ""}`;
21692
22058
  const doubleEncoded = escapeUri(normalizedPath);
21693
22059
  return doubleEncoded.replace(/%2F/g, "/");
21694
22060
  }
21695
- return path3;
22061
+ return path4;
21696
22062
  }
21697
22063
  validateResolvedCredentials(credentials) {
21698
22064
  if (typeof credentials !== "object" || typeof credentials.accessKeyId !== "string" || typeof credentials.secretAccessKey !== "string") {
@@ -27687,8 +28053,8 @@ var init_createConfigValueProvider = __esm({
27687
28053
  return endpoint.url.href;
27688
28054
  }
27689
28055
  if ("hostname" in endpoint) {
27690
- const { protocol, hostname, port, path: path3 } = endpoint;
27691
- return `${protocol}//${hostname}${port ? ":" + port : ""}${path3}`;
28056
+ const { protocol, hostname, port, path: path4 } = endpoint;
28057
+ return `${protocol}//${hostname}${port ? ":" + port : ""}${path4}`;
27692
28058
  }
27693
28059
  }
27694
28060
  return endpoint;
@@ -27904,14 +28270,14 @@ var init_readFile = __esm({
27904
28270
  "node_modules/@smithy/shared-ini-file-loader/dist-es/readFile.js"() {
27905
28271
  filePromises = {};
27906
28272
  fileIntercept = {};
27907
- readFile2 = (path3, options) => {
27908
- if (fileIntercept[path3] !== void 0) {
27909
- return fileIntercept[path3];
28273
+ readFile2 = (path4, options) => {
28274
+ if (fileIntercept[path4] !== void 0) {
28275
+ return fileIntercept[path4];
27910
28276
  }
27911
- if (!filePromises[path3] || options?.ignoreCache) {
27912
- filePromises[path3] = readFile(path3, "utf8");
28277
+ if (!filePromises[path4] || options?.ignoreCache) {
28278
+ filePromises[path4] = readFile(path4, "utf8");
27913
28279
  }
27914
- return filePromises[path3];
28280
+ return filePromises[path4];
27915
28281
  };
27916
28282
  }
27917
28283
  });
@@ -28020,8 +28386,8 @@ var init_externalDataInterceptor = __esm({
28020
28386
  getFileRecord() {
28021
28387
  return fileIntercept;
28022
28388
  },
28023
- interceptFile(path3, contents) {
28024
- fileIntercept[path3] = Promise.resolve(contents);
28389
+ interceptFile(path4, contents) {
28390
+ fileIntercept[path4] = Promise.resolve(contents);
28025
28391
  },
28026
28392
  getTokenRecord() {
28027
28393
  return tokenIntercept;
@@ -33819,7 +34185,7 @@ Set AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI
33819
34185
  if (token) {
33820
34186
  request2.headers.Authorization = token;
33821
34187
  } else if (tokenFile) {
33822
- request2.headers.Authorization = (await fs.readFile(tokenFile)).toString();
34188
+ request2.headers.Authorization = (await fs2.readFile(tokenFile)).toString();
33823
34189
  }
33824
34190
  try {
33825
34191
  const result = await requestHandler.handle(request2);
@@ -43291,9 +43657,28 @@ function _getFailStrategy() {
43291
43657
  function _hashPlaintext(plaintext, secret) {
43292
43658
  const trimmed = plaintext.trim();
43293
43659
  if (secret) {
43294
- return crypto2.createHmac("sha256", secret).update(trimmed, "utf-8").digest("hex");
43660
+ return cryptoNode.createHmac("sha256", secret).update(trimmed, "utf-8").digest("hex");
43661
+ }
43662
+ return cryptoNode.createHash("sha256").update(trimmed, "utf-8").digest("hex");
43663
+ }
43664
+ function _vaultKey(token) {
43665
+ return `mask:${config.MASK_TENANT_ID}:${token}`;
43666
+ }
43667
+ function _vaultRevKey(ptHash) {
43668
+ return `mask-rev:${config.MASK_TENANT_ID}:${ptHash}`;
43669
+ }
43670
+ function _vaultHashKey(token) {
43671
+ return `mask-hash:${config.MASK_TENANT_ID}:${token}`;
43672
+ }
43673
+ function _unwrapPayload(raw) {
43674
+ if (raw && raw.startsWith("{")) {
43675
+ try {
43676
+ const obj = JSON.parse(raw);
43677
+ if (obj.ct) return obj.ct;
43678
+ } catch {
43679
+ }
43295
43680
  }
43296
- return crypto2.createHash("sha256").update(trimmed, "utf-8").digest("hex");
43681
+ return raw;
43297
43682
  }
43298
43683
  function getVault() {
43299
43684
  if (_vaultInstance === null) {
@@ -43334,10 +43719,17 @@ async function encode(rawText, options = {}) {
43334
43719
  return existingToken;
43335
43720
  }
43336
43721
  }
43337
- const token = await generateFPEToken(text, options.entityType || "UNKNOWN");
43722
+ const token = await generateDPToken(text, options.entityType || "UNKNOWN");
43338
43723
  const ciphertext = cryptoEngine.encrypt(text);
43724
+ const existingCiphertext = await vault.retrieve(token);
43725
+ if (existingCiphertext !== null) {
43726
+ const existingHash = await vault.getPtHashForToken(token);
43727
+ if (existingHash && existingHash !== ptHash) {
43728
+ throw new TokenCollisionError(token, existingHash, ptHash);
43729
+ }
43730
+ }
43339
43731
  const ttl = options.ttl || DEFAULT_TTL;
43340
- await vault.store(token, ciphertext, ttl, ptHash);
43732
+ await vault.store(token, ciphertext, ttl, ptHash, options.metadata || null);
43341
43733
  if (options.searchBuckets && options.searchBuckets.length > 0) {
43342
43734
  for (const bType of options.searchBuckets) {
43343
43735
  let bucketVal;
@@ -43359,8 +43751,8 @@ async function decode(token) {
43359
43751
  throw new DecodeError("Token not found or expired");
43360
43752
  }
43361
43753
  try {
43362
- const crypto6 = await getCryptoEngineAsync();
43363
- const result = crypto6.decrypt(ciphertext);
43754
+ const crypto7 = await getCryptoEngineAsync();
43755
+ const result = crypto7.decrypt(ciphertext);
43364
43756
  getAuditLogger().log("decode", token);
43365
43757
  return result;
43366
43758
  } catch (e6) {
@@ -43412,14 +43804,15 @@ var init_vault = __esm({
43412
43804
  MemoryVault = class extends BaseVault {
43413
43805
  constructor() {
43414
43806
  super();
43807
+ this._cleanupTimer = null;
43415
43808
  this._store = /* @__PURE__ */ new Map();
43416
43809
  this._reverseStore = /* @__PURE__ */ new Map();
43810
+ this._cleanupTimer = setInterval(() => this._cleanup(), 6e4);
43811
+ if (this._cleanupTimer && typeof this._cleanupTimer.unref === "function") {
43812
+ this._cleanupTimer.unref();
43813
+ }
43417
43814
  }
43418
43815
  _cleanup() {
43419
- const cleanupFreq = config.MASK_VAULT_CLEANUP_FREQUENCY;
43420
- if (Math.random() > cleanupFreq) {
43421
- return;
43422
- }
43423
43816
  const now = Date.now() / 1e3;
43424
43817
  for (const [token, entry] of this._store.entries()) {
43425
43818
  if (now > entry.expiry) {
@@ -43430,27 +43823,41 @@ var init_vault = __esm({
43430
43823
  }
43431
43824
  }
43432
43825
  }
43433
- async store(token, plaintext, ttlSeconds, ptHash = null) {
43434
- this._cleanup();
43826
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43827
+ if (!this._store.has(token) && this._store.size >= config.MASK_VAULT_MAX_MEMORY_KEYS) {
43828
+ const firstKey = this._store.keys().next().value;
43829
+ if (firstKey !== void 0) {
43830
+ const oldEntry = this._store.get(firstKey);
43831
+ this._store.delete(firstKey);
43832
+ if (oldEntry?.ptHash && this._reverseStore.get(oldEntry.ptHash) === firstKey) {
43833
+ this._reverseStore.delete(oldEntry.ptHash);
43834
+ }
43835
+ }
43836
+ }
43837
+ const existing = this._store.get(token);
43838
+ if (existing) this._store.delete(token);
43435
43839
  this._store.set(token, {
43436
- plaintext,
43840
+ plaintext: ciphertext,
43437
43841
  expiry: Date.now() / 1e3 + ttlSeconds,
43438
- ptHash
43842
+ ptHash,
43843
+ metadata: { ...existing?.metadata || {}, ...metadata || {} }
43439
43844
  });
43440
43845
  if (ptHash) {
43441
43846
  this._reverseStore.set(ptHash, token);
43442
43847
  }
43443
43848
  }
43444
43849
  async getTokenByPlaintextHash(ptHash) {
43445
- this._cleanup();
43446
43850
  const token = this._reverseStore.get(ptHash);
43447
43851
  if (token && this._store.has(token)) {
43448
43852
  return token;
43449
43853
  }
43450
43854
  return null;
43451
43855
  }
43856
+ async getPtHashForToken(token) {
43857
+ const entry = this._store.get(token);
43858
+ return entry?.ptHash ?? null;
43859
+ }
43452
43860
  async retrieve(token) {
43453
- this._cleanup();
43454
43861
  const entry = this._store.get(token);
43455
43862
  if (!entry) {
43456
43863
  return null;
@@ -43506,13 +43913,14 @@ var init_vault = __esm({
43506
43913
  throw new MaskVaultConnectionError(`Failed to connect to Redis: ${e6}`);
43507
43914
  }
43508
43915
  }
43509
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
43916
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43510
43917
  try {
43511
43918
  const pipeline = this._client.pipeline();
43512
- pipeline.set(`mask:${token}`, ciphertext, "EX", ttlSeconds);
43919
+ const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
43920
+ pipeline.set(_vaultKey(token), payload, "EX", ttlSeconds);
43513
43921
  if (ptHash) {
43514
- pipeline.set(`mask-rev:${ptHash}`, token, "EX", ttlSeconds);
43515
- pipeline.set(`mask-hash:${token}`, ptHash, "EX", ttlSeconds);
43922
+ pipeline.set(_vaultRevKey(ptHash), token, "EX", ttlSeconds);
43923
+ pipeline.set(_vaultHashKey(token), ptHash, "EX", ttlSeconds);
43516
43924
  }
43517
43925
  const results = await pipeline.exec();
43518
43926
  if (results) {
@@ -43524,14 +43932,21 @@ var init_vault = __esm({
43524
43932
  throw new MaskVaultConnectionError(`Redis error: ${e6}`);
43525
43933
  }
43526
43934
  }
43935
+ async getPtHashForToken(token) {
43936
+ try {
43937
+ return await this._client.get(_vaultHashKey(token));
43938
+ } catch {
43939
+ return null;
43940
+ }
43941
+ }
43527
43942
  async getTokenByPlaintextHash(ptHash) {
43528
43943
  try {
43529
- const token = await this._client.get(`mask-rev:${ptHash}`);
43944
+ const token = await this._client.get(_vaultRevKey(ptHash));
43530
43945
  if (token) {
43531
- if (await this._client.exists(`mask:${token}`)) {
43946
+ if (await this._client.exists(_vaultKey(token))) {
43532
43947
  return token;
43533
43948
  } else {
43534
- await this._client.del(`mask-rev:${ptHash}`);
43949
+ await this._client.del(_vaultRevKey(ptHash));
43535
43950
  }
43536
43951
  }
43537
43952
  return null;
@@ -43544,7 +43959,8 @@ var init_vault = __esm({
43544
43959
  }
43545
43960
  async retrieve(token) {
43546
43961
  try {
43547
- return await this._client.get(`mask:${token}`);
43962
+ const raw = await this._client.get(_vaultKey(token));
43963
+ return raw ? _unwrapPayload(raw) : null;
43548
43964
  } catch (e6) {
43549
43965
  if (_getFailStrategy() === "closed") {
43550
43966
  throw new MaskVaultConnectionError(`Redis read failed: ${e6}`);
@@ -43554,12 +43970,12 @@ var init_vault = __esm({
43554
43970
  }
43555
43971
  async delete(token) {
43556
43972
  try {
43557
- const ptHash = await this._client.get(`mask-hash:${token}`);
43973
+ const ptHash = await this._client.get(_vaultHashKey(token));
43558
43974
  const pipeline = this._client.pipeline();
43559
- pipeline.del(`mask:${token}`);
43560
- pipeline.del(`mask-hash:${token}`);
43975
+ pipeline.del(_vaultKey(token));
43976
+ pipeline.del(_vaultHashKey(token));
43561
43977
  if (ptHash) {
43562
- pipeline.del(`mask-rev:${ptHash}`);
43978
+ pipeline.del(_vaultRevKey(ptHash));
43563
43979
  }
43564
43980
  await pipeline.exec();
43565
43981
  } catch (e6) {
@@ -43598,39 +44014,26 @@ var init_vault = __esm({
43598
44014
  this._client = DynamoDBDocument.from(baseClient);
43599
44015
  console.info(`DynamoDBVault connected to table ${this._tableName} in ${this._region}`);
43600
44016
  }
43601
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
44017
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43602
44018
  const { TransactWriteCommand, PutCommand } = __require("@aws-sdk/lib-dynamodb");
43603
44019
  const now = Math.floor(Date.now() / 1e3);
43604
44020
  const ttlVal = now + ttlSeconds;
43605
- const item = {
43606
- token: `mask:${token}`,
44021
+ const primaryItem = {
44022
+ token: _vaultKey(token),
43607
44023
  ciphertext,
43608
- ttl: ttlVal,
43609
- ptr_hash: ptHash || void 0
44024
+ ttl: ttlVal
43610
44025
  };
44026
+ if (ptHash) primaryItem.ptr_hash = ptHash;
44027
+ if (metadata) primaryItem.meta_json = JSON.stringify(metadata);
43611
44028
  if (ptHash) {
43612
44029
  try {
43613
44030
  await this._client.send(new TransactWriteCommand({
43614
44031
  TransactItems: [
44032
+ { Put: { TableName: this._tableName, Item: primaryItem } },
43615
44033
  {
43616
44034
  Put: {
43617
44035
  TableName: this._tableName,
43618
- Item: {
43619
- token: `mask:${token}`,
43620
- ciphertext,
43621
- ttl: ttlVal,
43622
- ptr_hash: ptHash
43623
- }
43624
- }
43625
- },
43626
- {
43627
- Put: {
43628
- TableName: this._tableName,
43629
- Item: {
43630
- token: `mask-rev:${ptHash}`,
43631
- ciphertext: token,
43632
- ttl: ttlVal
43633
- }
44036
+ Item: { token: _vaultRevKey(ptHash), ciphertext: token, ttl: ttlVal }
43634
44037
  }
43635
44038
  }
43636
44039
  ]
@@ -43641,7 +44044,7 @@ var init_vault = __esm({
43641
44044
  }
43642
44045
  } else {
43643
44046
  try {
43644
- await this._client.send(new PutCommand({ TableName: this._tableName, Item: item }));
44047
+ await this._client.send(new PutCommand({ TableName: this._tableName, Item: primaryItem }));
43645
44048
  } catch (e6) {
43646
44049
  throw new MaskVaultConnectionError(`DynamoDB individual write failed: ${e6}`);
43647
44050
  }
@@ -43653,12 +44056,12 @@ var init_vault = __esm({
43653
44056
  const now = Math.floor(Date.now() / 1e3);
43654
44057
  const resp = await this._client.send(new GetCommand({
43655
44058
  TableName: this._tableName,
43656
- Key: { token: `mask-rev:${ptHash}` }
44059
+ Key: { token: _vaultRevKey(ptHash) }
43657
44060
  }));
43658
44061
  const item = resp.Item;
43659
44062
  if (!item) return null;
43660
44063
  if (now > (item.ttl || 0)) {
43661
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${ptHash}` } }));
44064
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(ptHash) } }));
43662
44065
  return null;
43663
44066
  }
43664
44067
  const token = item.ciphertext;
@@ -43674,16 +44077,16 @@ var init_vault = __esm({
43674
44077
  const now = Math.floor(Date.now() / 1e3);
43675
44078
  const resp = await this._client.send(new GetCommand({
43676
44079
  TableName: this._tableName,
43677
- Key: { token: `mask:${token}` }
44080
+ Key: { token: _vaultKey(token) }
43678
44081
  }));
43679
44082
  const item = resp.Item;
43680
44083
  if (!item) return null;
43681
44084
  if (now > (item.ttl || 0)) {
43682
44085
  const ptHash = item.ptr_hash;
43683
44086
  if (ptHash) {
43684
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${ptHash}` } }));
44087
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(ptHash) } }));
43685
44088
  }
43686
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask:${token}` } }));
44089
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultKey(token) } }));
43687
44090
  return null;
43688
44091
  }
43689
44092
  return item.ciphertext;
@@ -43692,18 +44095,30 @@ var init_vault = __esm({
43692
44095
  return null;
43693
44096
  }
43694
44097
  }
44098
+ async getPtHashForToken(token) {
44099
+ try {
44100
+ const { GetCommand } = __require("@aws-sdk/lib-dynamodb");
44101
+ const resp = await this._client.send(new GetCommand({
44102
+ TableName: this._tableName,
44103
+ Key: { token: _vaultKey(token) }
44104
+ }));
44105
+ return resp.Item?.ptr_hash ?? null;
44106
+ } catch {
44107
+ return null;
44108
+ }
44109
+ }
43695
44110
  async delete(token) {
43696
44111
  try {
43697
44112
  const { GetCommand, DeleteCommand } = __require("@aws-sdk/lib-dynamodb");
43698
44113
  const resp = await this._client.send(new GetCommand({
43699
44114
  TableName: this._tableName,
43700
- Key: { token: `mask:${token}` }
44115
+ Key: { token: _vaultKey(token) }
43701
44116
  }));
43702
44117
  const item = resp.Item;
43703
44118
  if (item && item.ptr_hash) {
43704
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask-rev:${item.ptr_hash}` } }));
44119
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultRevKey(item.ptr_hash) } }));
43705
44120
  }
43706
- await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: `mask:${token}` } }));
44121
+ await this._client.send(new DeleteCommand({ TableName: this._tableName, Key: { token: _vaultKey(token) } }));
43707
44122
  } catch (e6) {
43708
44123
  if (_getFailStrategy() === "closed") throw new MaskVaultConnectionError(`DynamoDB delete failed: ${e6}`);
43709
44124
  }
@@ -43722,20 +44137,29 @@ var init_vault = __esm({
43722
44137
  throw new MaskVaultConnectionError(`Failed to connect to Memcached: ${e6}`);
43723
44138
  }
43724
44139
  }
43725
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
44140
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43726
44141
  try {
43727
- await this._client.set(`mask:${token}`, Buffer.from(ciphertext), { expires: ttlSeconds });
44142
+ const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
44143
+ await this._client.set(_vaultKey(token), Buffer.from(payload), { expires: ttlSeconds });
43728
44144
  if (ptHash) {
43729
- await this._client.set(`mask-rev:${ptHash}`, Buffer.from(token), { expires: ttlSeconds });
43730
- await this._client.set(`mask-hash:${token}`, Buffer.from(ptHash), { expires: ttlSeconds });
44145
+ await this._client.set(_vaultRevKey(ptHash), Buffer.from(token), { expires: ttlSeconds });
44146
+ await this._client.set(_vaultHashKey(token), Buffer.from(ptHash), { expires: ttlSeconds });
43731
44147
  }
43732
44148
  } catch (e6) {
43733
44149
  throw new MaskVaultConnectionError(`Memcached error: ${e6}`);
43734
44150
  }
43735
44151
  }
44152
+ async getPtHashForToken(token) {
44153
+ try {
44154
+ const { value } = await this._client.get(_vaultHashKey(token));
44155
+ return value ? value.toString() : null;
44156
+ } catch {
44157
+ return null;
44158
+ }
44159
+ }
43736
44160
  async getTokenByPlaintextHash(ptHash) {
43737
44161
  try {
43738
- const { value } = await this._client.get(`mask-rev:${ptHash}`);
44162
+ const { value } = await this._client.get(_vaultRevKey(ptHash));
43739
44163
  if (!value) return null;
43740
44164
  const token = value.toString();
43741
44165
  return await this.retrieve(token) !== null ? token : null;
@@ -43746,8 +44170,9 @@ var init_vault = __esm({
43746
44170
  }
43747
44171
  async retrieve(token) {
43748
44172
  try {
43749
- const { value } = await this._client.get(`mask:${token}`);
43750
- return value ? value.toString() : null;
44173
+ const { value } = await this._client.get(_vaultKey(token));
44174
+ if (!value) return null;
44175
+ return _unwrapPayload(value.toString());
43751
44176
  } catch (e6) {
43752
44177
  if (_getFailStrategy() === "closed") throw new MaskVaultConnectionError(`Memcached read failed: ${e6}`);
43753
44178
  return null;
@@ -43755,12 +44180,12 @@ var init_vault = __esm({
43755
44180
  }
43756
44181
  async delete(token) {
43757
44182
  try {
43758
- const { value } = await this._client.get(`mask-hash:${token}`);
44183
+ const { value } = await this._client.get(_vaultHashKey(token));
43759
44184
  const ptHash = value ? value.toString() : null;
43760
- await this._client.delete(`mask:${token}`);
43761
- await this._client.delete(`mask-hash:${token}`);
44185
+ await this._client.delete(_vaultKey(token));
44186
+ await this._client.delete(_vaultHashKey(token));
43762
44187
  if (ptHash) {
43763
- await this._client.delete(`mask-rev:${ptHash}`);
44188
+ await this._client.delete(_vaultRevKey(ptHash));
43764
44189
  }
43765
44190
  } catch (e6) {
43766
44191
  if (_getFailStrategy() === "closed") throw new MaskVaultConnectionError(`Memcached delete failed: ${e6}`);
@@ -53478,11 +53903,11 @@ var require_mime_types = __commonJS({
53478
53903
  }
53479
53904
  return exts[0];
53480
53905
  }
53481
- function lookup(path3) {
53482
- if (!path3 || typeof path3 !== "string") {
53906
+ function lookup(path4) {
53907
+ if (!path4 || typeof path4 !== "string") {
53483
53908
  return false;
53484
53909
  }
53485
- var extension2 = extname("x." + path3).toLowerCase().substr(1);
53910
+ var extension2 = extname("x." + path4).toLowerCase().substr(1);
53486
53911
  if (!extension2) {
53487
53912
  return false;
53488
53913
  }
@@ -54549,13 +54974,13 @@ var require_form_data = __commonJS({
54549
54974
  "node_modules/form-data/lib/form_data.js"(exports2, module) {
54550
54975
  var CombinedStream = require_combined_stream();
54551
54976
  var util = __require("util");
54552
- var path3 = __require("path");
54977
+ var path4 = __require("path");
54553
54978
  var http = __require("http");
54554
54979
  var https = __require("https");
54555
54980
  var parseUrl2 = __require("url").parse;
54556
- var fs3 = __require("fs");
54981
+ var fs4 = __require("fs");
54557
54982
  var Stream = __require("stream").Stream;
54558
- var crypto6 = __require("crypto");
54983
+ var crypto7 = __require("crypto");
54559
54984
  var mime = require_mime_types();
54560
54985
  var asynckit = require_asynckit();
54561
54986
  var setToStringTag = require_es_set_tostringtag();
@@ -54620,7 +55045,7 @@ var require_form_data = __commonJS({
54620
55045
  if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
54621
55046
  callback(null, value.end + 1 - (value.start ? value.start : 0));
54622
55047
  } else {
54623
- fs3.stat(value.path, function(err2, stat) {
55048
+ fs4.stat(value.path, function(err2, stat) {
54624
55049
  if (err2) {
54625
55050
  callback(err2);
54626
55051
  return;
@@ -54677,11 +55102,11 @@ var require_form_data = __commonJS({
54677
55102
  FormData2.prototype._getContentDisposition = function(value, options) {
54678
55103
  var filename;
54679
55104
  if (typeof options.filepath === "string") {
54680
- filename = path3.normalize(options.filepath).replace(/\\/g, "/");
55105
+ filename = path4.normalize(options.filepath).replace(/\\/g, "/");
54681
55106
  } else if (options.filename || value && (value.name || value.path)) {
54682
- filename = path3.basename(options.filename || value && (value.name || value.path));
55107
+ filename = path4.basename(options.filename || value && (value.name || value.path));
54683
55108
  } else if (value && value.readable && hasOwn(value, "httpVersion")) {
54684
- filename = path3.basename(value.client._httpMessage.path || "");
55109
+ filename = path4.basename(value.client._httpMessage.path || "");
54685
55110
  }
54686
55111
  if (filename) {
54687
55112
  return 'filename="' + filename + '"';
@@ -54761,7 +55186,7 @@ var require_form_data = __commonJS({
54761
55186
  return Buffer.concat([dataBuffer, Buffer.from(this._lastBoundary())]);
54762
55187
  };
54763
55188
  FormData2.prototype._generateBoundary = function() {
54764
- this._boundary = "--------------------------" + crypto6.randomBytes(12).toString("hex");
55189
+ this._boundary = "--------------------------" + crypto7.randomBytes(12).toString("hex");
54765
55190
  };
54766
55191
  FormData2.prototype.getLengthSync = function() {
54767
55192
  var knownLength = this._overheadLength + this._valueLength;
@@ -55451,7 +55876,7 @@ var require_follow_redirects = __commonJS({
55451
55876
  var require_axios = __commonJS({
55452
55877
  "node_modules/axios/dist/node/axios.cjs"(exports2, module) {
55453
55878
  var FormData$1 = require_form_data();
55454
- var crypto6 = __require("crypto");
55879
+ var crypto7 = __require("crypto");
55455
55880
  var url = __require("url");
55456
55881
  var proxyFromEnv = require_proxy_from_env();
55457
55882
  var http = __require("http");
@@ -55466,7 +55891,7 @@ var require_axios = __commonJS({
55466
55891
  return e6 && typeof e6 === "object" && "default" in e6 ? e6 : { "default": e6 };
55467
55892
  }
55468
55893
  var FormData__default = /* @__PURE__ */ _interopDefaultLegacy(FormData$1);
55469
- var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto6);
55894
+ var crypto__default = /* @__PURE__ */ _interopDefaultLegacy(crypto7);
55470
55895
  var url__default = /* @__PURE__ */ _interopDefaultLegacy(url);
55471
55896
  var proxyFromEnv__default = /* @__PURE__ */ _interopDefaultLegacy(proxyFromEnv);
55472
55897
  var http__default = /* @__PURE__ */ _interopDefaultLegacy(http);
@@ -55985,9 +56410,9 @@ var require_axios = __commonJS({
55985
56410
  function removeBrackets(key) {
55986
56411
  return utils$1.endsWith(key, "[]") ? key.slice(0, -2) : key;
55987
56412
  }
55988
- function renderKey(path3, key, dots) {
55989
- if (!path3) return key;
55990
- return path3.concat(key).map(function each(token, i6) {
56413
+ function renderKey(path4, key, dots) {
56414
+ if (!path4) return key;
56415
+ return path4.concat(key).map(function each(token, i6) {
55991
56416
  token = removeBrackets(token);
55992
56417
  return !dots && i6 ? "[" + token + "]" : token;
55993
56418
  }).join(dots ? "." : "");
@@ -56040,13 +56465,13 @@ var require_axios = __commonJS({
56040
56465
  }
56041
56466
  return value;
56042
56467
  }
56043
- function defaultVisitor(value, key, path3) {
56468
+ function defaultVisitor(value, key, path4) {
56044
56469
  let arr = value;
56045
56470
  if (utils$1.isReactNative(formData) && utils$1.isReactNativeBlob(value)) {
56046
- formData.append(renderKey(path3, key, dots), convertValue(value));
56471
+ formData.append(renderKey(path4, key, dots), convertValue(value));
56047
56472
  return false;
56048
56473
  }
56049
- if (value && !path3 && typeof value === "object") {
56474
+ if (value && !path4 && typeof value === "object") {
56050
56475
  if (utils$1.endsWith(key, "{}")) {
56051
56476
  key = metaTokens ? key : key.slice(0, -2);
56052
56477
  value = JSON.stringify(value);
@@ -56065,7 +56490,7 @@ var require_axios = __commonJS({
56065
56490
  if (isVisitable(value)) {
56066
56491
  return true;
56067
56492
  }
56068
- formData.append(renderKey(path3, key, dots), convertValue(value));
56493
+ formData.append(renderKey(path4, key, dots), convertValue(value));
56069
56494
  return false;
56070
56495
  }
56071
56496
  const stack = [];
@@ -56074,16 +56499,16 @@ var require_axios = __commonJS({
56074
56499
  convertValue,
56075
56500
  isVisitable
56076
56501
  });
56077
- function build(value, path3) {
56502
+ function build(value, path4) {
56078
56503
  if (utils$1.isUndefined(value)) return;
56079
56504
  if (stack.indexOf(value) !== -1) {
56080
- throw Error("Circular reference detected in " + path3.join("."));
56505
+ throw Error("Circular reference detected in " + path4.join("."));
56081
56506
  }
56082
56507
  stack.push(value);
56083
56508
  utils$1.forEach(value, function each(el, key) {
56084
- const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path3, exposedHelpers);
56509
+ const result = !(utils$1.isUndefined(el) || el === null) && visitor.call(formData, el, utils$1.isString(key) ? key.trim() : key, path4, exposedHelpers);
56085
56510
  if (result === true) {
56086
- build(el, path3 ? path3.concat(key) : [key]);
56511
+ build(el, path4 ? path4.concat(key) : [key]);
56087
56512
  }
56088
56513
  });
56089
56514
  stack.pop();
@@ -56271,7 +56696,7 @@ var require_axios = __commonJS({
56271
56696
  };
56272
56697
  function toURLEncodedForm(data, options) {
56273
56698
  return toFormData(data, new platform2.classes.URLSearchParams(), {
56274
- visitor: function(value, key, path3, helpers) {
56699
+ visitor: function(value, key, path4, helpers) {
56275
56700
  if (platform2.isNode && utils$1.isBuffer(value)) {
56276
56701
  this.append(key, value.toString("base64"));
56277
56702
  return false;
@@ -56299,11 +56724,11 @@ var require_axios = __commonJS({
56299
56724
  return obj;
56300
56725
  }
56301
56726
  function formDataToJSON(formData) {
56302
- function buildPath(path3, value, target, index) {
56303
- let name = path3[index++];
56727
+ function buildPath(path4, value, target, index) {
56728
+ let name = path4[index++];
56304
56729
  if (name === "__proto__") return true;
56305
56730
  const isNumericKey = Number.isFinite(+name);
56306
- const isLast = index >= path3.length;
56731
+ const isLast = index >= path4.length;
56307
56732
  name = !name && utils$1.isArray(target) ? target.length : name;
56308
56733
  if (isLast) {
56309
56734
  if (utils$1.hasOwnProp(target, name)) {
@@ -56316,7 +56741,7 @@ var require_axios = __commonJS({
56316
56741
  if (!target[name] || !utils$1.isObject(target[name])) {
56317
56742
  target[name] = [];
56318
56743
  }
56319
- const result = buildPath(path3, value, target[name], index);
56744
+ const result = buildPath(path4, value, target[name], index);
56320
56745
  if (result && utils$1.isArray(target[name])) {
56321
56746
  target[name] = arrayToObject(target[name]);
56322
56747
  }
@@ -57627,9 +58052,9 @@ var require_axios = __commonJS({
57627
58052
  auth = urlUsername + ":" + urlPassword;
57628
58053
  }
57629
58054
  auth && headers.delete("authorization");
57630
- let path3;
58055
+ let path4;
57631
58056
  try {
57632
- path3 = buildURL(
58057
+ path4 = buildURL(
57633
58058
  parsed.pathname + parsed.search,
57634
58059
  config2.params,
57635
58060
  config2.paramsSerializer
@@ -57647,7 +58072,7 @@ var require_axios = __commonJS({
57647
58072
  false
57648
58073
  );
57649
58074
  const options = {
57650
- path: path3,
58075
+ path: path4,
57651
58076
  method,
57652
58077
  headers: headers.toJSON(),
57653
58078
  agents: { http: config2.httpAgent, https: config2.httpsAgent },
@@ -57892,14 +58317,14 @@ var require_axios = __commonJS({
57892
58317
  var cookies = platform2.hasStandardBrowserEnv ? (
57893
58318
  // Standard browser envs support document.cookie
57894
58319
  {
57895
- write(name, value, expires, path3, domain, secure, sameSite) {
58320
+ write(name, value, expires, path4, domain, secure, sameSite) {
57896
58321
  if (typeof document === "undefined") return;
57897
58322
  const cookie = [`${name}=${encodeURIComponent(value)}`];
57898
58323
  if (utils$1.isNumber(expires)) {
57899
58324
  cookie.push(`expires=${new Date(expires).toUTCString()}`);
57900
58325
  }
57901
- if (utils$1.isString(path3)) {
57902
- cookie.push(`path=${path3}`);
58326
+ if (utils$1.isString(path4)) {
58327
+ cookie.push(`path=${path4}`);
57903
58328
  }
57904
58329
  if (utils$1.isString(domain)) {
57905
58330
  cookie.push(`domain=${domain}`);
@@ -59245,7 +59670,7 @@ var init_transformers_scanner = __esm({
59245
59670
  );
59246
59671
  }
59247
59672
  if (!this._pool) {
59248
- const workerPath = path.resolve(__dirname, "nlp_worker.js");
59673
+ const workerPath = path2.resolve(__dirname, "nlp_worker.js");
59249
59674
  const maxThreads = Math.max(1, Math.min(os.cpus().length - 1, 4));
59250
59675
  this._pool = new Piscina({
59251
59676
  filename: workerPath,
@@ -59842,12 +60267,12 @@ var MaskClient = class _MaskClient {
59842
60267
  */
59843
60268
  static async create(options = {}) {
59844
60269
  const vault = options.vault || getVault();
59845
- const crypto6 = options.crypto || await getCryptoEngineAsync();
60270
+ const crypto7 = options.crypto || await getCryptoEngineAsync();
59846
60271
  const scanner = options.scanner || getScanner();
59847
60272
  const auditLogger = options.auditLogger || getAuditLogger();
59848
60273
  return new _MaskClient({
59849
60274
  vault,
59850
- crypto: crypto6,
60275
+ crypto: crypto7,
59851
60276
  scanner,
59852
60277
  auditLogger,
59853
60278
  ttl: options.ttl