@vollcrypt/db-guard 0.9.0 → 0.9.1
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/LICENSE +16 -0
- package/LICENSE-COMMERCIAL.md +71 -0
- package/LICENSE-GPL +674 -0
- package/dist/blind-index.d.ts +1 -7
- package/dist/blind-index.js +3 -26
- package/dist/cli.js +3 -3
- package/dist/drivers.js +7 -8
- package/dist/drizzle.js +18 -20
- package/dist/index.d.ts +2 -6
- package/dist/index.js +4 -12
- package/dist/kms.d.ts +10 -0
- package/dist/kms.js +50 -0
- package/dist/mongoose.d.ts +1 -1
- package/dist/mongoose.js +12 -13
- package/dist/prisma.d.ts +4 -24
- package/dist/prisma.js +18 -174
- package/dist/provenance.json +21 -21
- package/dist/provenance.json.sig +2 -1
- package/dist/sbom.json +21 -21
- package/dist/sbom.json.sig +1 -2
- package/dist/security.d.ts +8 -0
- package/dist/security.js +124 -0
- package/dist/typeorm.d.ts +1 -1
- package/dist/typeorm.js +7 -9
- package/package.json +27 -2
package/dist/security.js
CHANGED
|
@@ -65,6 +65,11 @@ exports.resetAuditLoggerForTesting = resetAuditLoggerForTesting;
|
|
|
65
65
|
exports.logDecryption = logDecryption;
|
|
66
66
|
exports.decryptWithSecurity = decryptWithSecurity;
|
|
67
67
|
exports.parseCiphertext = parseCiphertext;
|
|
68
|
+
exports.computeBlindIndex = computeBlindIndex;
|
|
69
|
+
exports.encryptValue = encryptValue;
|
|
70
|
+
exports.decryptValue = decryptValue;
|
|
71
|
+
exports.rewriteQueryWhere = rewriteQueryWhere;
|
|
72
|
+
exports.addBlindIndexes = addBlindIndexes;
|
|
68
73
|
const async_hooks_1 = require("async_hooks");
|
|
69
74
|
const crypto = __importStar(require("crypto"));
|
|
70
75
|
const fs = __importStar(require("fs"));
|
|
@@ -589,3 +594,122 @@ function parseCiphertext(stored) {
|
|
|
589
594
|
}
|
|
590
595
|
throw new Error("Vollcrypt Security: Legacy unversioned ciphertexts are deprecated and unsupported.");
|
|
591
596
|
}
|
|
597
|
+
/**
|
|
598
|
+
* Computes a hardened, frequency-resistant blind index for a database field.
|
|
599
|
+
*/
|
|
600
|
+
function computeBlindIndex(value, rootSalt, columnName) {
|
|
601
|
+
if (value === null || value === undefined)
|
|
602
|
+
return value;
|
|
603
|
+
const plaintext = typeof value === 'string' ? value : JSON.stringify(value);
|
|
604
|
+
const columnNameBuf = Buffer.from(columnName, 'utf8');
|
|
605
|
+
// 1. Derive column-specific key using HKDF-SHA256
|
|
606
|
+
const derivedColumnKey = deriveHkdf(rootSalt, null, columnNameBuf, 32);
|
|
607
|
+
try {
|
|
608
|
+
// 2. Compute the final blind index using the derived column key
|
|
609
|
+
const plaintextBuf = Buffer.from(plaintext, 'utf8');
|
|
610
|
+
const blindIndex = deriveHkdf(derivedColumnKey, null, plaintextBuf, 32);
|
|
611
|
+
return blindIndex.toString('hex');
|
|
612
|
+
}
|
|
613
|
+
finally {
|
|
614
|
+
// 3. RAM Security: Zeroize the derived key immediately (Anti-Core Dump)
|
|
615
|
+
derivedColumnKey.fill(0);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function encryptValue(val, key, version) {
|
|
619
|
+
if (val === null || val === undefined)
|
|
620
|
+
return val;
|
|
621
|
+
const context = exports.dbGuardContextStore.getStore();
|
|
622
|
+
const tId = context?.tenantId || 'global';
|
|
623
|
+
if (key.every(b => b === 0) || getFailClosedStatus(tId)) {
|
|
624
|
+
throw new Error(`Vollcrypt Security: Fail-Closed mode is active for tenant "${tId}". Encryption blocked.`);
|
|
625
|
+
}
|
|
626
|
+
const plaintext = typeof val === 'string' ? val : JSON.stringify(val);
|
|
627
|
+
const plaintextBuf = Buffer.from(plaintext, 'utf8');
|
|
628
|
+
const encrypted = exports.CRYPTO_ALGORITHMS['1'].encrypt(plaintextBuf, key);
|
|
629
|
+
// RAM Security: Zeroize the plaintext buffer immediately
|
|
630
|
+
plaintextBuf.fill(0);
|
|
631
|
+
return `VOLLVALT:v${version}:${encrypted.toString('base64')}`;
|
|
632
|
+
}
|
|
633
|
+
function decryptValue(stored, keys) {
|
|
634
|
+
if (typeof stored !== 'string') {
|
|
635
|
+
return stored;
|
|
636
|
+
}
|
|
637
|
+
const parsed = parseCiphertext(stored);
|
|
638
|
+
if (!parsed) {
|
|
639
|
+
return stored;
|
|
640
|
+
}
|
|
641
|
+
const { algoId, version, base64Data } = parsed;
|
|
642
|
+
const key = keys[version];
|
|
643
|
+
if (!key) {
|
|
644
|
+
throw new Error(`Decryption key version "${version}" not found in registered keys`);
|
|
645
|
+
}
|
|
646
|
+
if (key.every(b => b === 0)) {
|
|
647
|
+
throw new Error(`Vollcrypt Security: Decryption blocked. Key version "${version}" is zeroized due to a Fail-Closed event.`);
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
const encryptedBuf = Buffer.from(base64Data, 'base64');
|
|
651
|
+
const decryptor = exports.CRYPTO_ALGORITHMS[algoId];
|
|
652
|
+
if (!decryptor) {
|
|
653
|
+
throw new Error(`Unsupported decryption algorithm ID "${algoId}"`);
|
|
654
|
+
}
|
|
655
|
+
const decrypted = decryptor.decrypt(encryptedBuf, key);
|
|
656
|
+
const plaintext = decrypted.toString('utf8');
|
|
657
|
+
// RAM Security: Zeroize the decrypted buffer
|
|
658
|
+
decrypted.fill(0);
|
|
659
|
+
try {
|
|
660
|
+
return JSON.parse(plaintext);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
return plaintext;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
catch (err) {
|
|
667
|
+
throw new Error(`Failed to decrypt field value: ${err.message}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function rewriteQueryWhere(where, fields, rootSalt, modelName) {
|
|
671
|
+
if (!where || typeof where !== 'object')
|
|
672
|
+
return;
|
|
673
|
+
for (const field of fields) {
|
|
674
|
+
if (where[field] !== undefined) {
|
|
675
|
+
const val = where[field];
|
|
676
|
+
const bidxField = `${field}_bidx`;
|
|
677
|
+
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
678
|
+
where[bidxField] = computeBlindIndex(val, rootSalt, `${modelName}.${field}`);
|
|
679
|
+
delete where[field];
|
|
680
|
+
}
|
|
681
|
+
else if (val && typeof val === 'object') {
|
|
682
|
+
if (val.equals !== undefined) {
|
|
683
|
+
where[bidxField] = {
|
|
684
|
+
equals: computeBlindIndex(val.equals, rootSalt, `${modelName}.${field}`),
|
|
685
|
+
};
|
|
686
|
+
delete where[field];
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Recurse into compound logical operators
|
|
692
|
+
const operators = ['AND', 'OR', 'NOT'];
|
|
693
|
+
for (const op of operators) {
|
|
694
|
+
if (Array.isArray(where[op])) {
|
|
695
|
+
where[op].forEach((item) => rewriteQueryWhere(item, fields, rootSalt, modelName));
|
|
696
|
+
}
|
|
697
|
+
else if (where[op] && typeof where[op] === 'object') {
|
|
698
|
+
rewriteQueryWhere(where[op], fields, rootSalt, modelName);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function addBlindIndexes(data, fields, rootSalt, modelName) {
|
|
703
|
+
if (!data || typeof data !== 'object')
|
|
704
|
+
return;
|
|
705
|
+
if (Array.isArray(data)) {
|
|
706
|
+
data.forEach((item) => addBlindIndexes(item, fields, rootSalt, modelName));
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
for (const field of fields) {
|
|
710
|
+
if (data[field] !== undefined && data[field] !== null) {
|
|
711
|
+
const bidxField = `${field}_bidx`;
|
|
712
|
+
data[bidxField] = computeBlindIndex(data[field], rootSalt, `${modelName}.${field}`);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
package/dist/typeorm.d.ts
CHANGED
package/dist/typeorm.js
CHANGED
|
@@ -35,9 +35,6 @@ var __runInitializers = (this && this.__runInitializers) || function (thisArg, i
|
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
37
|
exports.createTypeOrmSubscriber = createTypeOrmSubscriber;
|
|
38
|
-
const typeorm_1 = require("typeorm");
|
|
39
|
-
const prisma_1 = require("./prisma");
|
|
40
|
-
const blind_index_1 = require("./blind-index");
|
|
41
38
|
const security_1 = require("./security");
|
|
42
39
|
function getKeys(options) {
|
|
43
40
|
let keys;
|
|
@@ -56,6 +53,7 @@ function getKeys(options) {
|
|
|
56
53
|
return { keys, activeVersion };
|
|
57
54
|
}
|
|
58
55
|
function createTypeOrmSubscriber(options) {
|
|
56
|
+
const { EventSubscriber } = require('typeorm');
|
|
59
57
|
const { keys, activeVersion } = getKeys(options);
|
|
60
58
|
const activeKey = keys[activeVersion];
|
|
61
59
|
if (!activeKey) {
|
|
@@ -63,7 +61,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
63
61
|
}
|
|
64
62
|
(0, security_1.registerKeysForZeroization)(keys);
|
|
65
63
|
let VollcryptDbGuardSubscriber = (() => {
|
|
66
|
-
let _classDecorators = [
|
|
64
|
+
let _classDecorators = [EventSubscriber()];
|
|
67
65
|
let _classDescriptor;
|
|
68
66
|
let _classExtraInitializers = [];
|
|
69
67
|
let _classThis;
|
|
@@ -90,7 +88,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
90
88
|
for (const field of bidxFields) {
|
|
91
89
|
if (event.entity[field] !== undefined && event.entity[field] !== null) {
|
|
92
90
|
const bidxField = `${field}_bidx`;
|
|
93
|
-
event.entity[bidxField] = (0,
|
|
91
|
+
event.entity[bidxField] = (0, security_1.computeBlindIndex)(event.entity[field], options.blindIndexes.rootSalt, `${entityName}.${field}`);
|
|
94
92
|
}
|
|
95
93
|
}
|
|
96
94
|
}
|
|
@@ -98,7 +96,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
98
96
|
// Encrypt fields
|
|
99
97
|
for (const field of fields) {
|
|
100
98
|
if (event.entity[field] !== undefined && event.entity[field] !== null) {
|
|
101
|
-
event.entity[field] = (0,
|
|
99
|
+
event.entity[field] = (0, security_1.encryptValue)(event.entity[field], activeKey, activeVersion);
|
|
102
100
|
}
|
|
103
101
|
}
|
|
104
102
|
}
|
|
@@ -114,7 +112,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
114
112
|
for (const field of bidxFields) {
|
|
115
113
|
if (event.entity[field] !== undefined && event.entity[field] !== null) {
|
|
116
114
|
const bidxField = `${field}_bidx`;
|
|
117
|
-
event.entity[bidxField] = (0,
|
|
115
|
+
event.entity[bidxField] = (0, security_1.computeBlindIndex)(event.entity[field], options.blindIndexes.rootSalt, `${entityName}.${field}`);
|
|
118
116
|
}
|
|
119
117
|
}
|
|
120
118
|
}
|
|
@@ -122,7 +120,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
122
120
|
// Encrypt fields
|
|
123
121
|
for (const field of fields) {
|
|
124
122
|
if (event.entity[field] !== undefined && event.entity[field] !== null) {
|
|
125
|
-
event.entity[field] = (0,
|
|
123
|
+
event.entity[field] = (0, security_1.encryptValue)(event.entity[field], activeKey, activeVersion);
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
126
|
}
|
|
@@ -136,7 +134,7 @@ function createTypeOrmSubscriber(options) {
|
|
|
136
134
|
for (const field of fields) {
|
|
137
135
|
if (entity[field] !== undefined && entity[field] !== null) {
|
|
138
136
|
try {
|
|
139
|
-
entity[field] = (0, security_1.decryptWithSecurity)(entity[field], (val) => (0,
|
|
137
|
+
entity[field] = (0, security_1.decryptWithSecurity)(entity[field], (val) => (0, security_1.decryptValue)(val, keys), entityName, field, entity.id || entity._id, options);
|
|
140
138
|
}
|
|
141
139
|
catch (err) {
|
|
142
140
|
throw new Error(`TypeORM db-guard failed to decrypt field "${field}": ${err.message}`);
|
package/package.json
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vollcrypt/db-guard",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Database field-level encryption integrations for Vollcrypt (Prisma, Mongoose, Drizzle, TypeORM)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
8
|
"vollcrypt-db-guard": "dist/cli.js"
|
|
9
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./prisma": {
|
|
16
|
+
"types": "./dist/prisma.d.ts",
|
|
17
|
+
"default": "./dist/prisma.js"
|
|
18
|
+
},
|
|
19
|
+
"./mongoose": {
|
|
20
|
+
"types": "./dist/mongoose.d.ts",
|
|
21
|
+
"default": "./dist/mongoose.js"
|
|
22
|
+
},
|
|
23
|
+
"./drizzle": {
|
|
24
|
+
"types": "./dist/drizzle.d.ts",
|
|
25
|
+
"default": "./dist/drizzle.js"
|
|
26
|
+
},
|
|
27
|
+
"./typeorm": {
|
|
28
|
+
"types": "./dist/typeorm.d.ts",
|
|
29
|
+
"default": "./dist/typeorm.js"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
10
32
|
"files": [
|
|
11
33
|
"dist",
|
|
12
34
|
"README.md",
|
|
13
|
-
"compliance-config.json"
|
|
35
|
+
"compliance-config.json",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"LICENSE-COMMERCIAL.md",
|
|
38
|
+
"LICENSE-GPL"
|
|
14
39
|
],
|
|
15
40
|
"publishConfig": {
|
|
16
41
|
"access": "public"
|