mask-privacy 4.1.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,7 +1,7 @@
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
6
  var cryptoNode = require('crypto');
7
7
  var fs = require('fs');
@@ -36,7 +36,7 @@ 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
41
  var cryptoNode__namespace = /*#__PURE__*/_interopNamespace(cryptoNode);
42
42
  var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
@@ -129,6 +129,11 @@ var init_config = __esm({
129
129
  get MASK_ENCRYPTION_KEY() {
130
130
  return process2__namespace.env.MASK_ENCRYPTION_KEY || null;
131
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
+ },
132
137
  get MASK_MASTER_KEY() {
133
138
  return process2__namespace.env.MASK_MASTER_KEY || process2__namespace.env.MASK_ENCRYPTION_KEY || "";
134
139
  },
@@ -160,6 +165,9 @@ var init_config = __esm({
160
165
  get MASK_VAULT_CLEANUP_FREQUENCY() {
161
166
  return getEnvFloat("MASK_VAULT_CLEANUP_FREQUENCY", 0.01);
162
167
  },
168
+ get MASK_VAULT_MAX_MEMORY_KEYS() {
169
+ return getEnvInt("MASK_VAULT_MAX_MEMORY_KEYS", 1e5);
170
+ },
163
171
  // --- BACKEND CONNECTIONS ---
164
172
  get MASK_REDIS_URL() {
165
173
  return process2__namespace.env.MASK_REDIS_URL || "redis://localhost:6379/0";
@@ -199,7 +207,7 @@ var init_config = __esm({
199
207
  return process2__namespace.env.MASK_SCANNER_URL || "http://localhost:5001/analyze";
200
208
  },
201
209
  get MASK_MODEL_CACHE_DIR() {
202
- 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");
203
211
  },
204
212
  // --- TELEMETRY & AUDIT ---
205
213
  get MASK_AUDIT_LOG_STRICT() {
@@ -224,6 +232,16 @@ var init_key_provider = __esm({
224
232
  "src/core/key_provider.ts"() {
225
233
  init_config();
226
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
+ }
227
245
  };
228
246
  EnvKeyProvider = class extends BaseKeyProvider {
229
247
  async getEncryptionKey() {
@@ -239,6 +257,10 @@ var init_key_provider = __esm({
239
257
  let key = config.MASK_MASTER_KEY;
240
258
  return key || null;
241
259
  }
260
+ /** Return MASK_KEYRING from environment (default behaviour). */
261
+ async getKeyring() {
262
+ return config.MASK_KEYRING || null;
263
+ }
242
264
  };
243
265
  providerInstance = null;
244
266
  }
@@ -698,6 +720,23 @@ function looksLikeToken(value) {
698
720
  }
699
721
  return false;
700
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
+ }
701
740
  var TOKEN_PATTERN;
702
741
  var init_fpe_utils = __esm({
703
742
  "src/core/fpe_utils.ts"() {
@@ -732,7 +771,7 @@ function resetMasterKey() {
732
771
  }
733
772
  async function _getAesKey() {
734
773
  const masterKey = await _getMasterKey();
735
- return cryptoNode__namespace.createHash("sha256").update(masterKey).digest();
774
+ return cryptoNode__namespace.createHmac("sha256", masterKey).update(config.MASK_TENANT_ID, "utf-8").digest();
736
775
  }
737
776
  async function _hmacHex(plaintext, n6 = 8) {
738
777
  const masterKey = await _getMasterKey();
@@ -740,17 +779,13 @@ async function _hmacHex(plaintext, n6 = 8) {
740
779
  return digest.slice(0, n6);
741
780
  }
742
781
  async function _getBijectiveTweak() {
743
- const masterKey = await _getMasterKey();
744
- let base = config.MASK_TENANT_ID;
745
782
  if (config.MASK_SALT_ROTATION !== "NONE") {
746
- const now = /* @__PURE__ */ new Date();
747
- if (config.MASK_SALT_ROTATION === "MONTHLY") {
748
- base += `-${now.getUTCFullYear()}-${now.getUTCMonth() + 1}`;
749
- } else if (config.MASK_SALT_ROTATION === "YEARLY") {
750
- base += `-${now.getUTCFullYear()}`;
751
- }
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
+ );
752
786
  }
753
- return cryptoNode__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();
754
789
  }
755
790
  async function _encryptBijectiveFF1(text) {
756
791
  const canonical = text.toLowerCase().trim();
@@ -3349,24 +3384,28 @@ function getCryptoEngine() {
3349
3384
  async function getCryptoEngineAsync() {
3350
3385
  return await CryptoEngine.getInstanceAsync();
3351
3386
  }
3352
- 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;
3353
3388
  var init_crypto = __esm({
3354
3389
  "src/core/crypto.ts"() {
3355
3390
  init_config();
3356
3391
  init_key_provider();
3357
3392
  init_exceptions();
3393
+ AES_KEY_BYTES = 32;
3358
3394
  GCM_IV_BYTES = 12;
3359
3395
  GCM_AUTH_TAG_BYTES = 16;
3360
3396
  GCM_ALGORITHM = "aes-256-gcm";
3361
- AES_GCM_PREFIX = "aes:";
3397
+ AES_V2_PREFIX = "aes:v2:";
3398
+ AES_GCM_PREFIX = "aes:v1:";
3399
+ AES_GCM_LEGACY_PREFIX = "aes:";
3362
3400
  _CryptoEngine = class _CryptoEngine {
3363
3401
  constructor() {
3364
- this._aesKey = null;
3402
+ this._keyring = /* @__PURE__ */ new Map();
3403
+ this._activeKeyId = "default";
3365
3404
  this._indexSecret = null;
3366
3405
  }
3367
- /**
3406
+ /**
3368
3407
  * Return the singleton instance, initialising it if necessary.
3369
- * This is asynchronous because key providers (KMS, etc.) might be async.
3408
+ * Async because Argon2id key derivation is async.
3370
3409
  */
3371
3410
  static async getInstanceAsync() {
3372
3411
  if (this._instance === null) {
@@ -3382,11 +3421,11 @@ var init_crypto = __esm({
3382
3421
  }
3383
3422
  return this._instance;
3384
3423
  }
3385
- /** Clear the singleton instance to force re-initialization (useful for key rotation). */
3424
+ /** Clear the singleton (useful for key rotation / tests). */
3386
3425
  static reset() {
3387
3426
  this._instance = null;
3388
3427
  }
3389
- async _init() {
3428
+ async _deriveAesKey(rawKey, keyId) {
3390
3429
  let argon2;
3391
3430
  try {
3392
3431
  argon2 = __require("argon2");
@@ -3395,38 +3434,72 @@ var init_crypto = __esm({
3395
3434
  "The 'argon2' package is required for Mask SDK cryptographic operations. Install with: npm install argon2"
3396
3435
  );
3397
3436
  }
3398
- const provider = getKeyProvider();
3399
- const keyFromProvider = await provider.getEncryptionKey();
3400
- let key;
3401
- if (!keyFromProvider) {
3402
- if (config.MASK_DEV_MODE) {
3403
- key = cryptoNode__namespace.randomBytes(32).toString("base64");
3404
- process.env.MASK_ENCRYPTION_KEY = key;
3405
- console.warn(
3406
- "MASK_DEV_MODE is enabled. Using a generated throwaway key. DO NOT USE THIS IN PRODUCTION \u2014 tokens will be lost on restart."
3407
- );
3408
- } else {
3409
- throw new Error(
3410
- "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."
3411
- );
3412
- }
3413
- } else {
3414
- key = keyFromProvider;
3415
- }
3416
- const kdfSaltStr = config.MASK_KDF_SALT + "-" + config.MASK_TENANT_ID;
3437
+ const kdfSaltStr = config.MASK_KDF_SALT + "-" + config.MASK_TENANT_ID + "-" + keyId;
3417
3438
  const kdfSaltBytes = cryptoNode__namespace.createHash("sha256").update(kdfSaltStr).digest().subarray(0, 16);
3418
- this._aesKey = await argon2.hash(key, {
3439
+ return await argon2.hash(rawKey, {
3419
3440
  type: argon2.argon2id,
3420
3441
  memoryCost: 19456,
3421
- // 19 MiB
3422
3442
  timeCost: 2,
3423
3443
  parallelism: 1,
3424
- hashLength: 32,
3444
+ hashLength: AES_KEY_BYTES,
3425
3445
  salt: kdfSaltBytes,
3426
3446
  raw: true
3427
- // return a Buffer, not a hash string
3428
3447
  });
3429
- const masterKey = await provider.getMasterKey() || key;
3448
+ }
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
+ }
3456
+ const provider = getKeyProvider();
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}`);
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];
3474
+ } else {
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;
3430
3503
  const indexSaltStr = config.MASK_BLIND_INDEX_SALT + "-" + config.MASK_TENANT_ID;
3431
3504
  const indexSaltBytes = cryptoNode__namespace.createHash("sha256").update(indexSaltStr).digest().subarray(0, 16);
3432
3505
  this._indexSecret = await argon2.hash(masterKey, {
@@ -3434,7 +3507,7 @@ var init_crypto = __esm({
3434
3507
  memoryCost: 19456,
3435
3508
  timeCost: 2,
3436
3509
  parallelism: 1,
3437
- hashLength: 32,
3510
+ hashLength: AES_KEY_BYTES,
3438
3511
  salt: indexSaltBytes,
3439
3512
  raw: true
3440
3513
  });
@@ -3446,36 +3519,56 @@ var init_crypto = __esm({
3446
3519
  }
3447
3520
  return this._indexSecret;
3448
3521
  }
3522
+ /** Encrypt plaintext using the active keyring key.
3523
+ * Envelope format: aes:v2:{keyId}:{base64(iv+authTag+ciphertext)}
3524
+ */
3449
3525
  encrypt(plaintext) {
3450
- if (!this._aesKey) {
3451
- throw new Error("CryptoEngine not initialised. AES key missing.");
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.`);
3452
3529
  }
3453
3530
  const iv = cryptoNode__namespace.randomBytes(GCM_IV_BYTES);
3454
- const cipher = cryptoNode__namespace.createCipheriv(GCM_ALGORITHM, this._aesKey, iv);
3455
- const encrypted = Buffer.concat([
3456
- cipher.update(plaintext, "utf8"),
3457
- cipher.final()
3458
- ]);
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()]);
3459
3534
  const authTag = cipher.getAuthTag();
3535
+ plaintextBuf.fill(0);
3460
3536
  const combined = Buffer.concat([iv, authTag, encrypted]);
3461
- return AES_GCM_PREFIX + combined.toString("base64");
3537
+ return `${AES_V2_PREFIX}${this._activeKeyId}:${combined.toString("base64")}`;
3462
3538
  }
3539
+ /** Decrypt ciphertext. Supports all historical envelope formats. */
3463
3540
  decrypt(ciphertext) {
3464
- if (!this._aesKey) {
3465
- throw new Error("CryptoEngine not initialised. AES key missing.");
3541
+ if (this._keyring.size === 0) {
3542
+ throw new Error("CryptoEngine not initialised.");
3466
3543
  }
3467
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
+ }
3468
3553
  if (ciphertext.startsWith(AES_GCM_PREFIX)) {
3469
- 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));
3470
3558
  }
3471
3559
  return this._decryptLegacyFernet(ciphertext);
3472
3560
  } catch (e6) {
3473
- 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);
3474
3562
  throw new exports.MaskDecryptionError("Decryption failed");
3475
3563
  }
3476
3564
  }
3477
- /** Decrypt an AES-256-GCM token (base64 encoded). */
3478
- _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
+ }
3479
3572
  const combined = Buffer.from(b64, "base64");
3480
3573
  if (combined.length < GCM_IV_BYTES + GCM_AUTH_TAG_BYTES) {
3481
3574
  throw new Error("Ciphertext too short for AES-GCM");
@@ -3483,22 +3576,11 @@ var init_crypto = __esm({
3483
3576
  const iv = combined.subarray(0, GCM_IV_BYTES);
3484
3577
  const authTag = combined.subarray(GCM_IV_BYTES, GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3485
3578
  const encrypted = combined.subarray(GCM_IV_BYTES + GCM_AUTH_TAG_BYTES);
3486
- const decipher = cryptoNode__namespace.createDecipheriv(GCM_ALGORITHM, this._aesKey, iv);
3579
+ const decipher = cryptoNode__namespace.createDecipheriv(GCM_ALGORITHM, aesKey, iv);
3487
3580
  decipher.setAuthTag(authTag);
3488
- const decrypted = Buffer.concat([
3489
- decipher.update(encrypted),
3490
- decipher.final()
3491
- ]);
3581
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
3492
3582
  return decrypted.toString("utf8");
3493
3583
  }
3494
- /**
3495
- * Attempt to decrypt a legacy Fernet-format token.
3496
- *
3497
- * Fernet format: Version (1) || Timestamp (8) || IV (16) || Ciphertext (var) || HMAC (32)
3498
- * All base64url-encoded.
3499
- *
3500
- * We try to use the `fernet` npm package if available, otherwise throw.
3501
- */
3502
3584
  _decryptLegacyFernet(ciphertext) {
3503
3585
  let fernet;
3504
3586
  try {
@@ -3559,16 +3641,15 @@ function getLogger(name) {
3559
3641
  error: (...args) => _log("error", ...args)
3560
3642
  };
3561
3643
  }
3562
- function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null) {
3644
+ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null, instanceId = "") {
3563
3645
  const event = {
3564
3646
  ts: Date.now() / 1e3,
3565
3647
  action,
3566
- // "encode" | "decode" | "expired" | "error"
3567
3648
  token,
3568
3649
  data_type: dataType,
3569
- // "email" | "phone" | "ssn" | "opaque"
3570
3650
  agent,
3571
- tool
3651
+ tool,
3652
+ instance_id: instanceId
3572
3653
  };
3573
3654
  if (extra) {
3574
3655
  Object.assign(event, _deepMask(extra));
@@ -3578,7 +3659,7 @@ function _makeEvent(action, token, dataType, agent = "", tool = "", extra = null
3578
3659
  function _deepMask(obj) {
3579
3660
  if (obj === null || obj === void 0) return obj;
3580
3661
  if (typeof obj === "string") {
3581
- return looksLikeToken(obj) ? obj : "[REDACTED]";
3662
+ return isUnambiguouslySafeToken(obj) ? obj : "[REDACTED]";
3582
3663
  }
3583
3664
  if (typeof obj !== "object") return obj;
3584
3665
  if (Array.isArray(obj)) {
@@ -3614,7 +3695,8 @@ var init_audit_logger = __esm({
3614
3695
  this._strictMode = config.MASK_AUDIT_LOG_STRICT;
3615
3696
  const rawKey = process.env.MASK_MASTER_KEY || process.env.MASK_ENCRYPTION_KEY || "";
3616
3697
  this._signingKey = cryptoNode__namespace.createHash("sha256").update(rawKey).digest();
3617
- this._prevSig = "0".repeat(64);
3698
+ this._instanceId = cryptoNode__namespace.randomUUID();
3699
+ this._prevSig = cryptoNode__namespace.createHmac("sha256", this._signingKey).update(this._instanceId, "utf-8").digest("hex");
3618
3700
  }
3619
3701
  static getInstance() {
3620
3702
  if (this._instance === null) {
@@ -3622,16 +3704,46 @@ var init_audit_logger = __esm({
3622
3704
  }
3623
3705
  return this._instance;
3624
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
+ }
3625
3736
  log(action, token, dataType = "opaque", agent = "", tool = "", extra = {}) {
3626
- const event = _makeEvent(action, token, dataType, agent, tool, extra);
3737
+ const event = _makeEvent(action, token, dataType, agent, tool, extra, this._instanceId);
3627
3738
  if (this._buffer.length >= this._maxBufferSize) {
3628
3739
  if (!this._bufferFullWarned) {
3629
3740
  _logger.warn(
3630
- `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.`
3631
3742
  );
3632
3743
  this._bufferFullWarned = true;
3633
3744
  }
3634
- this._flushSync();
3745
+ this._writeOverflow(event);
3746
+ return;
3635
3747
  }
3636
3748
  this._buffer.push(event);
3637
3749
  }
@@ -3661,18 +3773,20 @@ var init_audit_logger = __esm({
3661
3773
  await this._flush();
3662
3774
  }
3663
3775
  async _flush() {
3664
- if (this._isFlushing || this._buffer.length === 0) return;
3776
+ if (this._isFlushing) return;
3665
3777
  this._isFlushing = true;
3666
3778
  try {
3667
3779
  const events = [...this._buffer];
3668
3780
  this._buffer = [];
3669
3781
  this._bufferFullWarned = false;
3782
+ this._consumeOverflow(events);
3783
+ if (events.length === 0) return;
3670
3784
  const secureLogDir = process.env.MASK_SECURE_AUDIT_LOG_DIR || "";
3671
3785
  let secureStream = null;
3672
3786
  if (secureLogDir) {
3673
3787
  fs__namespace.mkdirSync(secureLogDir, { recursive: true });
3674
3788
  const dateStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3675
- const filePath = path__namespace.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3789
+ const filePath = path2__namespace.join(secureLogDir, `mask-audit-${dateStr}.ndjson`);
3676
3790
  try {
3677
3791
  secureStream = fs__namespace.createWriteStream(filePath, { flags: "a" });
3678
3792
  } catch {
@@ -3700,13 +3814,43 @@ var init_audit_logger = __esm({
3700
3814
  this._isFlushing = false;
3701
3815
  }
3702
3816
  }
3703
- /** 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
+ */
3704
3823
  _flushSync() {
3705
3824
  if (this._buffer.length === 0) return;
3706
3825
  const events = [...this._buffer];
3707
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
+ }
3708
3837
  for (const evt of events) {
3709
- 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
+ }
3710
3854
  }
3711
3855
  }
3712
3856
  };
@@ -18299,10 +18443,10 @@ var init_date_utils = __esm({
18299
18443
  };
18300
18444
  }
18301
18445
  });
18302
- var randomUUID;
18446
+ var randomUUID2;
18303
18447
  var init_randomUUID = __esm({
18304
18448
  "node_modules/@smithy/uuid/dist-es/randomUUID.js"() {
18305
- randomUUID = cryptoNode__namespace.default.randomUUID.bind(cryptoNode__namespace.default);
18449
+ randomUUID2 = cryptoNode__namespace.default.randomUUID.bind(cryptoNode__namespace.default);
18306
18450
  }
18307
18451
  });
18308
18452
 
@@ -18313,8 +18457,8 @@ var init_v4 = __esm({
18313
18457
  init_randomUUID();
18314
18458
  decimalToHex = Array.from({ length: 256 }, (_, i6) => i6.toString(16).padStart(2, "0"));
18315
18459
  v4 = () => {
18316
- if (randomUUID) {
18317
- return randomUUID();
18460
+ if (randomUUID2) {
18461
+ return randomUUID2();
18318
18462
  }
18319
18463
  const rnds = new Uint8Array(16);
18320
18464
  crypto.getRandomValues(rnds);
@@ -27993,7 +28137,7 @@ var init_getHomeDir = __esm({
27993
28137
  return "DEFAULT";
27994
28138
  };
27995
28139
  getHomeDir = () => {
27996
- const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${path.sep}` } = process.env;
28140
+ const { HOME, USERPROFILE, HOMEPATH, HOMEDRIVE = `C:${path2.sep}` } = process.env;
27997
28141
  if (HOME)
27998
28142
  return HOME;
27999
28143
  if (USERPROFILE)
@@ -28024,7 +28168,7 @@ var init_getSSOTokenFilepath = __esm({
28024
28168
  getSSOTokenFilepath = (id) => {
28025
28169
  const hasher = cryptoNode.createHash("sha1");
28026
28170
  const cacheName = hasher.update(id).digest("hex");
28027
- return path.join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
28171
+ return path2.join(getHomeDir(), ".aws", "sso", "cache", `${cacheName}.json`);
28028
28172
  };
28029
28173
  }
28030
28174
  });
@@ -28079,7 +28223,7 @@ var init_getConfigFilepath = __esm({
28079
28223
  "node_modules/@smithy/shared-ini-file-loader/dist-es/getConfigFilepath.js"() {
28080
28224
  init_getHomeDir();
28081
28225
  ENV_CONFIG_PATH = "AWS_CONFIG_FILE";
28082
- getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path.join(getHomeDir(), ".aws", "config");
28226
+ getConfigFilepath = () => process.env[ENV_CONFIG_PATH] || path2.join(getHomeDir(), ".aws", "config");
28083
28227
  }
28084
28228
  });
28085
28229
  var ENV_CREDENTIALS_PATH, getCredentialsFilepath;
@@ -28087,7 +28231,7 @@ var init_getCredentialsFilepath = __esm({
28087
28231
  "node_modules/@smithy/shared-ini-file-loader/dist-es/getCredentialsFilepath.js"() {
28088
28232
  init_getHomeDir();
28089
28233
  ENV_CREDENTIALS_PATH = "AWS_SHARED_CREDENTIALS_FILE";
28090
- getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path.join(getHomeDir(), ".aws", "credentials");
28234
+ getCredentialsFilepath = () => process.env[ENV_CREDENTIALS_PATH] || path2.join(getHomeDir(), ".aws", "credentials");
28091
28235
  }
28092
28236
  });
28093
28237
 
@@ -28179,11 +28323,11 @@ var init_loadSharedConfigFiles = __esm({
28179
28323
  const relativeHomeDirPrefix = "~/";
28180
28324
  let resolvedFilepath = filepath;
28181
28325
  if (filepath.startsWith(relativeHomeDirPrefix)) {
28182
- resolvedFilepath = path.join(homeDir, filepath.slice(2));
28326
+ resolvedFilepath = path2.join(homeDir, filepath.slice(2));
28183
28327
  }
28184
28328
  let resolvedConfigFilepath = configFilepath;
28185
28329
  if (configFilepath.startsWith(relativeHomeDirPrefix)) {
28186
- resolvedConfigFilepath = path.join(homeDir, configFilepath.slice(2));
28330
+ resolvedConfigFilepath = path2.join(homeDir, configFilepath.slice(2));
28187
28331
  }
28188
28332
  const parsedFiles = await Promise.all([
28189
28333
  readFile2(resolvedConfigFilepath, {
@@ -34448,10 +34592,10 @@ var init_getNodeModulesParentDirs = __esm({
34448
34592
  if (!dirname2) {
34449
34593
  return [cwd];
34450
34594
  }
34451
- const normalizedPath = path.normalize(dirname2);
34452
- const parts = normalizedPath.split(path.sep);
34595
+ const normalizedPath = path2.normalize(dirname2);
34596
+ const parts = normalizedPath.split(path2.sep);
34453
34597
  const nodeModulesIndex = parts.indexOf("node_modules");
34454
- 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;
34455
34599
  if (cwd === parentDir) {
34456
34600
  return [cwd];
34457
34601
  }
@@ -34503,7 +34647,7 @@ var init_getTypeScriptUserAgentPair = __esm({
34503
34647
  init_getNodeModulesParentDirs();
34504
34648
  init_getSanitizedDevTypeScriptVersion();
34505
34649
  init_getSanitizedTypeScriptVersion();
34506
- TS_PACKAGE_JSON = path.join("node_modules", "typescript", "package.json");
34650
+ TS_PACKAGE_JSON = path2.join("node_modules", "typescript", "package.json");
34507
34651
  getTypeScriptUserAgentPair = async () => {
34508
34652
  if (tscVersion === null) {
34509
34653
  return void 0;
@@ -34524,7 +34668,7 @@ var init_getTypeScriptUserAgentPair = __esm({
34524
34668
  let versionFromApp;
34525
34669
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
34526
34670
  try {
34527
- const appPackageJsonPath = path.join(nodeModulesParentDir, "package.json");
34671
+ const appPackageJsonPath = path2.join(nodeModulesParentDir, "package.json");
34528
34672
  const packageJson = await fs2.readFile(appPackageJsonPath, "utf-8");
34529
34673
  const { dependencies, devDependencies } = JSON.parse(packageJson);
34530
34674
  const version = devDependencies?.typescript ?? dependencies?.typescript;
@@ -34543,7 +34687,7 @@ var init_getTypeScriptUserAgentPair = __esm({
34543
34687
  let versionFromNodeModules;
34544
34688
  for (const nodeModulesParentDir of nodeModulesParentDirs) {
34545
34689
  try {
34546
- const tsPackageJsonPath = path.join(nodeModulesParentDir, TS_PACKAGE_JSON);
34690
+ const tsPackageJsonPath = path2.join(nodeModulesParentDir, TS_PACKAGE_JSON);
34547
34691
  const packageJson = await fs2.readFile(tsPackageJsonPath, "utf-8");
34548
34692
  const { version } = JSON.parse(packageJson);
34549
34693
  const sanitizedVersion2 = getSanitizedTypeScriptVersion(version);
@@ -38912,7 +39056,7 @@ var init_LoginCredentialsFetcher = __esm({
38912
39056
  }
38913
39057
  async saveToken(token) {
38914
39058
  const tokenFilePath = this.getTokenFilePath();
38915
- const directory = path.dirname(tokenFilePath);
39059
+ const directory = path2.dirname(tokenFilePath);
38916
39060
  try {
38917
39061
  await fs.promises.mkdir(directory, { recursive: true });
38918
39062
  } catch (error) {
@@ -38920,10 +39064,10 @@ var init_LoginCredentialsFetcher = __esm({
38920
39064
  await fs.promises.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
38921
39065
  }
38922
39066
  getTokenFilePath() {
38923
- 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");
38924
39068
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
38925
39069
  const loginSessionSha256 = cryptoNode.createHash("sha256").update(loginSessionBytes).digest("hex");
38926
- return path.join(directory, `${loginSessionSha256}.json`);
39070
+ return path2.join(directory, `${loginSessionSha256}.json`);
38927
39071
  }
38928
39072
  derToRawSignature(derSignature) {
38929
39073
  let offset = 2;
@@ -43542,6 +43686,25 @@ function _hashPlaintext(plaintext, secret) {
43542
43686
  }
43543
43687
  return cryptoNode__namespace.createHash("sha256").update(trimmed, "utf-8").digest("hex");
43544
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
+ }
43705
+ }
43706
+ return raw;
43707
+ }
43545
43708
  function getVault() {
43546
43709
  if (_vaultInstance === null) {
43547
43710
  const vaultType = config.MASK_VAULT_TYPE;
@@ -43666,14 +43829,15 @@ var init_vault = __esm({
43666
43829
  MemoryVault = class extends BaseVault {
43667
43830
  constructor() {
43668
43831
  super();
43832
+ this._cleanupTimer = null;
43669
43833
  this._store = /* @__PURE__ */ new Map();
43670
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
+ }
43671
43839
  }
43672
43840
  _cleanup() {
43673
- const cleanupFreq = config.MASK_VAULT_CLEANUP_FREQUENCY;
43674
- if (Math.random() > cleanupFreq) {
43675
- return;
43676
- }
43677
43841
  const now = Date.now() / 1e3;
43678
43842
  for (const [token, entry] of this._store.entries()) {
43679
43843
  if (now > entry.expiry) {
@@ -43685,8 +43849,18 @@ var init_vault = __esm({
43685
43849
  }
43686
43850
  }
43687
43851
  async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43688
- this._cleanup();
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
+ }
43689
43862
  const existing = this._store.get(token);
43863
+ if (existing) this._store.delete(token);
43690
43864
  this._store.set(token, {
43691
43865
  plaintext: ciphertext,
43692
43866
  expiry: Date.now() / 1e3 + ttlSeconds,
@@ -43698,7 +43872,6 @@ var init_vault = __esm({
43698
43872
  }
43699
43873
  }
43700
43874
  async getTokenByPlaintextHash(ptHash) {
43701
- this._cleanup();
43702
43875
  const token = this._reverseStore.get(ptHash);
43703
43876
  if (token && this._store.has(token)) {
43704
43877
  return token;
@@ -43710,7 +43883,6 @@ var init_vault = __esm({
43710
43883
  return entry?.ptHash ?? null;
43711
43884
  }
43712
43885
  async retrieve(token) {
43713
- this._cleanup();
43714
43886
  const entry = this._store.get(token);
43715
43887
  if (!entry) {
43716
43888
  return null;
@@ -43770,10 +43942,10 @@ var init_vault = __esm({
43770
43942
  try {
43771
43943
  const pipeline = this._client.pipeline();
43772
43944
  const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
43773
- pipeline.set(`mask:${token}`, payload, "EX", ttlSeconds);
43945
+ pipeline.set(_vaultKey(token), payload, "EX", ttlSeconds);
43774
43946
  if (ptHash) {
43775
- pipeline.set(`mask-rev:${ptHash}`, token, "EX", ttlSeconds);
43776
- pipeline.set(`mask-hash:${token}`, ptHash, "EX", ttlSeconds);
43947
+ pipeline.set(_vaultRevKey(ptHash), token, "EX", ttlSeconds);
43948
+ pipeline.set(_vaultHashKey(token), ptHash, "EX", ttlSeconds);
43777
43949
  }
43778
43950
  const results = await pipeline.exec();
43779
43951
  if (results) {
@@ -43787,19 +43959,19 @@ var init_vault = __esm({
43787
43959
  }
43788
43960
  async getPtHashForToken(token) {
43789
43961
  try {
43790
- return await this._client.get(`mask-hash:${token}`);
43962
+ return await this._client.get(_vaultHashKey(token));
43791
43963
  } catch {
43792
43964
  return null;
43793
43965
  }
43794
43966
  }
43795
43967
  async getTokenByPlaintextHash(ptHash) {
43796
43968
  try {
43797
- const token = await this._client.get(`mask-rev:${ptHash}`);
43969
+ const token = await this._client.get(_vaultRevKey(ptHash));
43798
43970
  if (token) {
43799
- if (await this._client.exists(`mask:${token}`)) {
43971
+ if (await this._client.exists(_vaultKey(token))) {
43800
43972
  return token;
43801
43973
  } else {
43802
- await this._client.del(`mask-rev:${ptHash}`);
43974
+ await this._client.del(_vaultRevKey(ptHash));
43803
43975
  }
43804
43976
  }
43805
43977
  return null;
@@ -43812,7 +43984,8 @@ var init_vault = __esm({
43812
43984
  }
43813
43985
  async retrieve(token) {
43814
43986
  try {
43815
- return await this._client.get(`mask:${token}`);
43987
+ const raw = await this._client.get(_vaultKey(token));
43988
+ return raw ? _unwrapPayload(raw) : null;
43816
43989
  } catch (e6) {
43817
43990
  if (_getFailStrategy() === "closed") {
43818
43991
  throw new exports.MaskVaultConnectionError(`Redis read failed: ${e6}`);
@@ -43822,12 +43995,12 @@ var init_vault = __esm({
43822
43995
  }
43823
43996
  async delete(token) {
43824
43997
  try {
43825
- const ptHash = await this._client.get(`mask-hash:${token}`);
43998
+ const ptHash = await this._client.get(_vaultHashKey(token));
43826
43999
  const pipeline = this._client.pipeline();
43827
- pipeline.del(`mask:${token}`);
43828
- pipeline.del(`mask-hash:${token}`);
44000
+ pipeline.del(_vaultKey(token));
44001
+ pipeline.del(_vaultHashKey(token));
43829
44002
  if (ptHash) {
43830
- pipeline.del(`mask-rev:${ptHash}`);
44003
+ pipeline.del(_vaultRevKey(ptHash));
43831
44004
  }
43832
44005
  await pipeline.exec();
43833
44006
  } catch (e6) {
@@ -43866,39 +44039,26 @@ var init_vault = __esm({
43866
44039
  this._client = DynamoDBDocument.from(baseClient);
43867
44040
  console.info(`DynamoDBVault connected to table ${this._tableName} in ${this._region}`);
43868
44041
  }
43869
- async store(token, ciphertext, ttlSeconds, ptHash = null) {
44042
+ async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
43870
44043
  const { TransactWriteCommand, PutCommand } = __require("@aws-sdk/lib-dynamodb");
43871
44044
  const now = Math.floor(Date.now() / 1e3);
43872
44045
  const ttlVal = now + ttlSeconds;
43873
- const item = {
43874
- token: `mask:${token}`,
44046
+ const primaryItem = {
44047
+ token: _vaultKey(token),
43875
44048
  ciphertext,
43876
- ttl: ttlVal,
43877
- ptr_hash: ptHash || void 0
44049
+ ttl: ttlVal
43878
44050
  };
44051
+ if (ptHash) primaryItem.ptr_hash = ptHash;
44052
+ if (metadata) primaryItem.meta_json = JSON.stringify(metadata);
43879
44053
  if (ptHash) {
43880
44054
  try {
43881
44055
  await this._client.send(new TransactWriteCommand({
43882
44056
  TransactItems: [
44057
+ { Put: { TableName: this._tableName, Item: primaryItem } },
43883
44058
  {
43884
44059
  Put: {
43885
44060
  TableName: this._tableName,
43886
- Item: {
43887
- token: `mask:${token}`,
43888
- ciphertext,
43889
- ttl: ttlVal,
43890
- ptr_hash: ptHash
43891
- }
43892
- }
43893
- },
43894
- {
43895
- Put: {
43896
- TableName: this._tableName,
43897
- Item: {
43898
- token: `mask-rev:${ptHash}`,
43899
- ciphertext: token,
43900
- ttl: ttlVal
43901
- }
44061
+ Item: { token: _vaultRevKey(ptHash), ciphertext: token, ttl: ttlVal }
43902
44062
  }
43903
44063
  }
43904
44064
  ]
@@ -43909,7 +44069,7 @@ var init_vault = __esm({
43909
44069
  }
43910
44070
  } else {
43911
44071
  try {
43912
- await this._client.send(new PutCommand({ TableName: this._tableName, Item: item }));
44072
+ await this._client.send(new PutCommand({ TableName: this._tableName, Item: primaryItem }));
43913
44073
  } catch (e6) {
43914
44074
  throw new exports.MaskVaultConnectionError(`DynamoDB individual write failed: ${e6}`);
43915
44075
  }
@@ -43921,12 +44081,12 @@ var init_vault = __esm({
43921
44081
  const now = Math.floor(Date.now() / 1e3);
43922
44082
  const resp = await this._client.send(new GetCommand({
43923
44083
  TableName: this._tableName,
43924
- Key: { token: `mask-rev:${ptHash}` }
44084
+ Key: { token: _vaultRevKey(ptHash) }
43925
44085
  }));
43926
44086
  const item = resp.Item;
43927
44087
  if (!item) return null;
43928
44088
  if (now > (item.ttl || 0)) {
43929
- 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) } }));
43930
44090
  return null;
43931
44091
  }
43932
44092
  const token = item.ciphertext;
@@ -43942,16 +44102,16 @@ var init_vault = __esm({
43942
44102
  const now = Math.floor(Date.now() / 1e3);
43943
44103
  const resp = await this._client.send(new GetCommand({
43944
44104
  TableName: this._tableName,
43945
- Key: { token: `mask:${token}` }
44105
+ Key: { token: _vaultKey(token) }
43946
44106
  }));
43947
44107
  const item = resp.Item;
43948
44108
  if (!item) return null;
43949
44109
  if (now > (item.ttl || 0)) {
43950
44110
  const ptHash = item.ptr_hash;
43951
44111
  if (ptHash) {
43952
- 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) } }));
43953
44113
  }
43954
- 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) } }));
43955
44115
  return null;
43956
44116
  }
43957
44117
  return item.ciphertext;
@@ -43965,7 +44125,7 @@ var init_vault = __esm({
43965
44125
  const { GetCommand } = __require("@aws-sdk/lib-dynamodb");
43966
44126
  const resp = await this._client.send(new GetCommand({
43967
44127
  TableName: this._tableName,
43968
- Key: { token: `mask:${token}` }
44128
+ Key: { token: _vaultKey(token) }
43969
44129
  }));
43970
44130
  return resp.Item?.ptr_hash ?? null;
43971
44131
  } catch {
@@ -43977,13 +44137,13 @@ var init_vault = __esm({
43977
44137
  const { GetCommand, DeleteCommand } = __require("@aws-sdk/lib-dynamodb");
43978
44138
  const resp = await this._client.send(new GetCommand({
43979
44139
  TableName: this._tableName,
43980
- Key: { token: `mask:${token}` }
44140
+ Key: { token: _vaultKey(token) }
43981
44141
  }));
43982
44142
  const item = resp.Item;
43983
44143
  if (item && item.ptr_hash) {
43984
- 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) } }));
43985
44145
  }
43986
- 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) } }));
43987
44147
  } catch (e6) {
43988
44148
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`DynamoDB delete failed: ${e6}`);
43989
44149
  }
@@ -44005,10 +44165,10 @@ var init_vault = __esm({
44005
44165
  async store(token, ciphertext, ttlSeconds, ptHash = null, metadata = null) {
44006
44166
  try {
44007
44167
  const payload = metadata ? JSON.stringify({ ct: ciphertext, meta: metadata }) : ciphertext;
44008
- await this._client.set(`mask:${token}`, Buffer.from(payload), { expires: ttlSeconds });
44168
+ await this._client.set(_vaultKey(token), Buffer.from(payload), { expires: ttlSeconds });
44009
44169
  if (ptHash) {
44010
- await this._client.set(`mask-rev:${ptHash}`, Buffer.from(token), { expires: ttlSeconds });
44011
- 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 });
44012
44172
  }
44013
44173
  } catch (e6) {
44014
44174
  throw new exports.MaskVaultConnectionError(`Memcached error: ${e6}`);
@@ -44016,7 +44176,7 @@ var init_vault = __esm({
44016
44176
  }
44017
44177
  async getPtHashForToken(token) {
44018
44178
  try {
44019
- const { value } = await this._client.get(`mask-hash:${token}`);
44179
+ const { value } = await this._client.get(_vaultHashKey(token));
44020
44180
  return value ? value.toString() : null;
44021
44181
  } catch {
44022
44182
  return null;
@@ -44024,7 +44184,7 @@ var init_vault = __esm({
44024
44184
  }
44025
44185
  async getTokenByPlaintextHash(ptHash) {
44026
44186
  try {
44027
- const { value } = await this._client.get(`mask-rev:${ptHash}`);
44187
+ const { value } = await this._client.get(_vaultRevKey(ptHash));
44028
44188
  if (!value) return null;
44029
44189
  const token = value.toString();
44030
44190
  return await this.retrieve(token) !== null ? token : null;
@@ -44035,8 +44195,9 @@ var init_vault = __esm({
44035
44195
  }
44036
44196
  async retrieve(token) {
44037
44197
  try {
44038
- const { value } = await this._client.get(`mask:${token}`);
44039
- return value ? value.toString() : null;
44198
+ const { value } = await this._client.get(_vaultKey(token));
44199
+ if (!value) return null;
44200
+ return _unwrapPayload(value.toString());
44040
44201
  } catch (e6) {
44041
44202
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`Memcached read failed: ${e6}`);
44042
44203
  return null;
@@ -44044,12 +44205,12 @@ var init_vault = __esm({
44044
44205
  }
44045
44206
  async delete(token) {
44046
44207
  try {
44047
- const { value } = await this._client.get(`mask-hash:${token}`);
44208
+ const { value } = await this._client.get(_vaultHashKey(token));
44048
44209
  const ptHash = value ? value.toString() : null;
44049
- await this._client.delete(`mask:${token}`);
44050
- await this._client.delete(`mask-hash:${token}`);
44210
+ await this._client.delete(_vaultKey(token));
44211
+ await this._client.delete(_vaultHashKey(token));
44051
44212
  if (ptHash) {
44052
- await this._client.delete(`mask-rev:${ptHash}`);
44213
+ await this._client.delete(_vaultRevKey(ptHash));
44053
44214
  }
44054
44215
  } catch (e6) {
44055
44216
  if (_getFailStrategy() === "closed") throw new exports.MaskVaultConnectionError(`Memcached delete failed: ${e6}`);
@@ -59534,7 +59695,7 @@ var init_transformers_scanner = __esm({
59534
59695
  );
59535
59696
  }
59536
59697
  if (!this._pool) {
59537
- const workerPath = path__namespace.resolve(__dirname, "nlp_worker.js");
59698
+ const workerPath = path2__namespace.resolve(__dirname, "nlp_worker.js");
59538
59699
  const maxThreads = Math.max(1, Math.min(os__namespace.cpus().length - 1, 4));
59539
59700
  this._pool = new Piscina({
59540
59701
  filename: workerPath,