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.d.mts +27 -19
- package/dist/index.d.ts +27 -19
- package/dist/index.js +327 -166
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +311 -150
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/config.ts +4 -0
- package/src/core/crypto.ts +152 -103
- package/src/core/fpe.ts +20 -10
- package/src/core/fpe_utils.ts +46 -0
- package/src/core/key_provider.ts +80 -0
- package/src/core/vault.ts +92 -67
- package/src/telemetry/audit_logger.ts +93 -15
- package/tests/security_hardening.test.ts +91 -0
- package/tests/vault_backends.test.ts +7 -7
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var process2 = require('process');
|
|
4
|
-
var
|
|
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
|
|
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 ||
|
|
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.
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
3424
|
+
/** Clear the singleton (useful for key rotation / tests). */
|
|
3386
3425
|
static reset() {
|
|
3387
3426
|
this._instance = null;
|
|
3388
3427
|
}
|
|
3389
|
-
async
|
|
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
|
|
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
|
-
|
|
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:
|
|
3444
|
+
hashLength: AES_KEY_BYTES,
|
|
3425
3445
|
salt: kdfSaltBytes,
|
|
3426
3446
|
raw: true
|
|
3427
|
-
// return a Buffer, not a hash string
|
|
3428
3447
|
});
|
|
3429
|
-
|
|
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:
|
|
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
|
-
|
|
3451
|
-
|
|
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,
|
|
3455
|
-
const
|
|
3456
|
-
|
|
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
|
|
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 (
|
|
3465
|
-
throw new Error("CryptoEngine not initialised.
|
|
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
|
-
|
|
3478
|
-
|
|
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,
|
|
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
|
|
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.
|
|
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}).
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
18446
|
+
var randomUUID2;
|
|
18303
18447
|
var init_randomUUID = __esm({
|
|
18304
18448
|
"node_modules/@smithy/uuid/dist-es/randomUUID.js"() {
|
|
18305
|
-
|
|
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 (
|
|
18317
|
-
return
|
|
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:${
|
|
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
|
|
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] ||
|
|
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] ||
|
|
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 =
|
|
28326
|
+
resolvedFilepath = path2.join(homeDir, filepath.slice(2));
|
|
28183
28327
|
}
|
|
28184
28328
|
let resolvedConfigFilepath = configFilepath;
|
|
28185
28329
|
if (configFilepath.startsWith(relativeHomeDirPrefix)) {
|
|
28186
|
-
resolvedConfigFilepath =
|
|
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 =
|
|
34452
|
-
const parts = normalizedPath.split(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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
|
|
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.
|
|
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(
|
|
43945
|
+
pipeline.set(_vaultKey(token), payload, "EX", ttlSeconds);
|
|
43774
43946
|
if (ptHash) {
|
|
43775
|
-
pipeline.set(
|
|
43776
|
-
pipeline.set(
|
|
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(
|
|
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(
|
|
43969
|
+
const token = await this._client.get(_vaultRevKey(ptHash));
|
|
43798
43970
|
if (token) {
|
|
43799
|
-
if (await this._client.exists(
|
|
43971
|
+
if (await this._client.exists(_vaultKey(token))) {
|
|
43800
43972
|
return token;
|
|
43801
43973
|
} else {
|
|
43802
|
-
await this._client.del(
|
|
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
|
-
|
|
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(
|
|
43998
|
+
const ptHash = await this._client.get(_vaultHashKey(token));
|
|
43826
43999
|
const pipeline = this._client.pipeline();
|
|
43827
|
-
pipeline.del(
|
|
43828
|
-
pipeline.del(
|
|
44000
|
+
pipeline.del(_vaultKey(token));
|
|
44001
|
+
pipeline.del(_vaultHashKey(token));
|
|
43829
44002
|
if (ptHash) {
|
|
43830
|
-
pipeline.del(
|
|
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
|
|
43874
|
-
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
44168
|
+
await this._client.set(_vaultKey(token), Buffer.from(payload), { expires: ttlSeconds });
|
|
44009
44169
|
if (ptHash) {
|
|
44010
|
-
await this._client.set(
|
|
44011
|
-
await this._client.set(
|
|
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(
|
|
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(
|
|
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(
|
|
44039
|
-
|
|
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(
|
|
44208
|
+
const { value } = await this._client.get(_vaultHashKey(token));
|
|
44048
44209
|
const ptHash = value ? value.toString() : null;
|
|
44049
|
-
await this._client.delete(
|
|
44050
|
-
await this._client.delete(
|
|
44210
|
+
await this._client.delete(_vaultKey(token));
|
|
44211
|
+
await this._client.delete(_vaultHashKey(token));
|
|
44051
44212
|
if (ptHash) {
|
|
44052
|
-
await this._client.delete(
|
|
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 =
|
|
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,
|