@vollcrypt/db-guard 0.1.2 → 0.9.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/blind-index.js +10 -6
- package/dist/drivers.js +47 -10
- package/dist/drizzle.js +5 -2
- package/dist/kms.js +8 -2
- package/dist/mongoose.js +22 -12
- package/dist/prisma.js +35 -11
- package/dist/provenance.json +29 -13
- package/dist/provenance.json.sig +1 -1
- package/dist/sbom.json +37 -13
- package/dist/sbom.json.sig +2 -2
- package/dist/security.d.ts +3 -3
- package/dist/security.js +99 -76
- package/dist/typeorm.js +5 -2
- package/package.json +80 -50
package/dist/blind-index.js
CHANGED
|
@@ -15,10 +15,14 @@ function computeBlindIndex(value, rootSalt, columnName) {
|
|
|
15
15
|
const columnNameBuf = Buffer.from(columnName, 'utf8');
|
|
16
16
|
// 1. Derive column-specific key using HKDF-SHA256
|
|
17
17
|
const derivedColumnKey = (0, security_1.deriveHkdf)(rootSalt, null, columnNameBuf, 32);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
try {
|
|
19
|
+
// 2. Compute the final blind index using the derived column key
|
|
20
|
+
const plaintextBuf = Buffer.from(plaintext, 'utf8');
|
|
21
|
+
const blindIndex = (0, security_1.deriveHkdf)(derivedColumnKey, null, plaintextBuf, 32);
|
|
22
|
+
return blindIndex.toString('hex');
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
// 3. RAM Security: Zeroize the derived key immediately (Anti-Core Dump)
|
|
26
|
+
derivedColumnKey.fill(0);
|
|
27
|
+
}
|
|
24
28
|
}
|
package/dist/drivers.js
CHANGED
|
@@ -5,37 +5,50 @@ exports.wrapOracleConnection = wrapOracleConnection;
|
|
|
5
5
|
const prisma_js_1 = require("./prisma.js");
|
|
6
6
|
const security_js_1 = require("./security.js");
|
|
7
7
|
function getKeys(options) {
|
|
8
|
-
let keys;
|
|
8
|
+
let keys = {};
|
|
9
9
|
let activeVersion;
|
|
10
10
|
if (Buffer.isBuffer(options.key)) {
|
|
11
|
-
keys = { '1': options.key };
|
|
11
|
+
keys = { '1': Buffer.from(options.key) };
|
|
12
12
|
activeVersion = '1';
|
|
13
13
|
}
|
|
14
14
|
else {
|
|
15
|
-
|
|
15
|
+
for (const [v, k] of Object.entries(options.key)) {
|
|
16
|
+
keys[v] = Buffer.from(k);
|
|
17
|
+
}
|
|
16
18
|
activeVersion = options.activeKeyVersion || Object.keys(keys)[0];
|
|
17
19
|
}
|
|
18
20
|
return { keys, activeVersion };
|
|
19
21
|
}
|
|
22
|
+
function cleanIdentifier(identifier) {
|
|
23
|
+
if (!identifier)
|
|
24
|
+
return identifier;
|
|
25
|
+
let cleaned = identifier.trim();
|
|
26
|
+
if ((cleaned.startsWith('"') && cleaned.endsWith('"')) ||
|
|
27
|
+
(cleaned.startsWith('`') && cleaned.endsWith('`')) ||
|
|
28
|
+
(cleaned.startsWith('[') && cleaned.endsWith(']'))) {
|
|
29
|
+
cleaned = cleaned.slice(1, -1);
|
|
30
|
+
}
|
|
31
|
+
return cleaned.trim();
|
|
32
|
+
}
|
|
20
33
|
function getParamColumns(sql) {
|
|
21
34
|
const sqlClean = sql.replace(/\s+/g, ' ').trim();
|
|
22
35
|
// Match INSERT INTO table (col1, col2) ...
|
|
23
|
-
const insertMatch = sqlClean.match(/INSERT\s+INTO\s+(\
|
|
36
|
+
const insertMatch = sqlClean.match(/INSERT\s+INTO\s+([a-zA-Z0-9_"`[\]]+)\s*\(([^)]+)\)/i);
|
|
24
37
|
if (insertMatch) {
|
|
25
|
-
const table = insertMatch[1];
|
|
26
|
-
const columns = insertMatch[2].split(',').map(c => c
|
|
38
|
+
const table = cleanIdentifier(insertMatch[1]);
|
|
39
|
+
const columns = insertMatch[2].split(',').map(c => cleanIdentifier(c));
|
|
27
40
|
return { table, columns };
|
|
28
41
|
}
|
|
29
42
|
// Match UPDATE table SET col1 = ?, col2 = ? ...
|
|
30
|
-
const updateMatch = sqlClean.match(/UPDATE\s+(\
|
|
43
|
+
const updateMatch = sqlClean.match(/UPDATE\s+([a-zA-Z0-9_"`[\]]+)\s+SET\s+([\s\S]+?)(?:\s+WHERE|$)/i);
|
|
31
44
|
if (updateMatch) {
|
|
32
|
-
const table = updateMatch[1];
|
|
45
|
+
const table = cleanIdentifier(updateMatch[1]);
|
|
33
46
|
const setParts = updateMatch[2].split(',');
|
|
34
47
|
const columns = [];
|
|
35
48
|
for (const part of setParts) {
|
|
36
|
-
const match = part.match(/(\
|
|
49
|
+
const match = part.match(/([a-zA-Z0-9_"`[\]]+)\s*=/);
|
|
37
50
|
if (match) {
|
|
38
|
-
columns.push(match[1]);
|
|
51
|
+
columns.push(cleanIdentifier(match[1]));
|
|
39
52
|
}
|
|
40
53
|
}
|
|
41
54
|
return { table, columns };
|
|
@@ -96,6 +109,30 @@ function wrapSqliteDatabase(db, options) {
|
|
|
96
109
|
const fieldsToEncrypt = options.entities[table] || [];
|
|
97
110
|
if (fieldsToEncrypt.length === 0)
|
|
98
111
|
return params;
|
|
112
|
+
// Case 1: single array parameter, e.g., stmt.run([val1, val2])
|
|
113
|
+
if (params.length === 1 && Array.isArray(params[0])) {
|
|
114
|
+
const arrayParams = params[0].map((param, index) => {
|
|
115
|
+
const colName = columns[index];
|
|
116
|
+
if (colName && fieldsToEncrypt.includes(colName)) {
|
|
117
|
+
return (0, prisma_js_1.encryptValue)(param, activeKey, activeVersion);
|
|
118
|
+
}
|
|
119
|
+
return param;
|
|
120
|
+
});
|
|
121
|
+
return [arrayParams];
|
|
122
|
+
}
|
|
123
|
+
// Case 2: single object parameter for named binds, e.g., stmt.run({ col1: val1 })
|
|
124
|
+
if (params.length === 1 && params[0] && typeof params[0] === 'object' && !Buffer.isBuffer(params[0])) {
|
|
125
|
+
const obj = { ...params[0] };
|
|
126
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
127
|
+
// Strip prefix character (@, :, $) if present
|
|
128
|
+
const cleanKey = key.replace(/^[@:$]/, '');
|
|
129
|
+
if (fieldsToEncrypt.includes(cleanKey)) {
|
|
130
|
+
obj[key] = (0, prisma_js_1.encryptValue)(val, activeKey, activeVersion);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return [obj];
|
|
134
|
+
}
|
|
135
|
+
// Case 3: multiple positional parameters, e.g., stmt.run(val1, val2)
|
|
99
136
|
return params.map((param, index) => {
|
|
100
137
|
const colName = columns[index];
|
|
101
138
|
if (colName && fieldsToEncrypt.includes(colName)) {
|
package/dist/drizzle.js
CHANGED
|
@@ -11,11 +11,14 @@ function getKeys(options) {
|
|
|
11
11
|
let keys;
|
|
12
12
|
let activeVersion;
|
|
13
13
|
if (Buffer.isBuffer(options.key)) {
|
|
14
|
-
keys = { '1': options.key };
|
|
14
|
+
keys = { '1': Buffer.from(options.key) };
|
|
15
15
|
activeVersion = '1';
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
keys =
|
|
18
|
+
keys = {};
|
|
19
|
+
for (const [v, k] of Object.entries(options.key)) {
|
|
20
|
+
keys[v] = Buffer.from(k);
|
|
21
|
+
}
|
|
19
22
|
activeVersion = options.activeKeyVersion || Object.keys(keys)[0];
|
|
20
23
|
}
|
|
21
24
|
return { keys, activeVersion };
|
package/dist/kms.js
CHANGED
|
@@ -106,7 +106,14 @@ class Pkcs11KmsProvider {
|
|
|
106
106
|
const pkcs11js = require('pkcs11js');
|
|
107
107
|
const pkcs11 = new pkcs11js.PKCS11();
|
|
108
108
|
pkcs11.load(this.config.libraryPath);
|
|
109
|
-
|
|
109
|
+
try {
|
|
110
|
+
pkcs11.C_Initialize();
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
if (!e.message?.includes('CRYPTOKI_ALREADY_INITIALIZED') && e.code !== 0x00000191 && e.code !== 401) {
|
|
114
|
+
throw e;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
110
117
|
const slots = pkcs11.C_GetSlotList(true);
|
|
111
118
|
const slotIndex = this.config.slotId !== undefined ? this.config.slotId : 0;
|
|
112
119
|
if (!slots || slots.length <= slotIndex) {
|
|
@@ -143,7 +150,6 @@ class Pkcs11KmsProvider {
|
|
|
143
150
|
finally {
|
|
144
151
|
pkcs11.C_Logout(session);
|
|
145
152
|
pkcs11.C_CloseSession(session);
|
|
146
|
-
pkcs11.C_Finalize();
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
catch (err) {
|
package/dist/mongoose.js
CHANGED
|
@@ -6,14 +6,16 @@ const blind_index_1 = require("./blind-index");
|
|
|
6
6
|
const security_1 = require("./security");
|
|
7
7
|
function mongooseDbGuard(schema, options) {
|
|
8
8
|
const { fields } = options;
|
|
9
|
-
let keys;
|
|
9
|
+
let keys = {};
|
|
10
10
|
let activeVersion;
|
|
11
11
|
if (Buffer.isBuffer(options.key)) {
|
|
12
|
-
keys = { '1': options.key };
|
|
12
|
+
keys = { '1': Buffer.from(options.key) };
|
|
13
13
|
activeVersion = '1';
|
|
14
14
|
}
|
|
15
15
|
else {
|
|
16
|
-
|
|
16
|
+
for (const [v, k] of Object.entries(options.key)) {
|
|
17
|
+
keys[v] = Buffer.from(k);
|
|
18
|
+
}
|
|
17
19
|
activeVersion = options.activeKeyVersion || Object.keys(keys)[0];
|
|
18
20
|
}
|
|
19
21
|
(0, security_1.registerKeysForZeroization)(keys);
|
|
@@ -28,11 +30,15 @@ function mongooseDbGuard(schema, options) {
|
|
|
28
30
|
return { keys: { '1': bgKey }, activeKey: bgKey, activeVersion: '1' };
|
|
29
31
|
}
|
|
30
32
|
}
|
|
31
|
-
if (
|
|
33
|
+
if (options.multiTenant && !tenantId) {
|
|
34
|
+
throw new Error("Vollcrypt Security: tenantId must be provided in multi-tenant mode.");
|
|
35
|
+
}
|
|
36
|
+
if (!options.multiTenant) {
|
|
32
37
|
return { keys, activeKey, activeVersion };
|
|
33
38
|
}
|
|
39
|
+
const tId = tenantId;
|
|
34
40
|
// Check Secure TTL Cache
|
|
35
|
-
const cachedActiveKey = (0, security_1.getCachedKey)(
|
|
41
|
+
const cachedActiveKey = (0, security_1.getCachedKey)(tId, activeVersion);
|
|
36
42
|
if (cachedActiveKey) {
|
|
37
43
|
return { keys: { [activeVersion]: cachedActiveKey }, activeKey: cachedActiveKey, activeVersion };
|
|
38
44
|
}
|
|
@@ -44,27 +50,31 @@ function mongooseDbGuard(schema, options) {
|
|
|
44
50
|
}
|
|
45
51
|
let tenantConfig;
|
|
46
52
|
if (multiTenant.tenants) {
|
|
47
|
-
tenantConfig = multiTenant.tenants[
|
|
53
|
+
tenantConfig = multiTenant.tenants[tId];
|
|
48
54
|
}
|
|
49
55
|
else if (multiTenant.getTenantConfig) {
|
|
50
|
-
tenantConfig = await multiTenant.getTenantConfig(
|
|
56
|
+
tenantConfig = await multiTenant.getTenantConfig(tId);
|
|
51
57
|
}
|
|
52
58
|
if (!tenantConfig) {
|
|
53
|
-
throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${
|
|
59
|
+
throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tId}".`);
|
|
54
60
|
}
|
|
55
|
-
const
|
|
61
|
+
const resolvedTenantKeysRaw = await (0, prisma_1.resolveKeys)({
|
|
56
62
|
...options,
|
|
57
63
|
key: tenantConfig.key,
|
|
58
64
|
kms: tenantConfig.kms
|
|
59
65
|
});
|
|
60
|
-
|
|
66
|
+
const resolvedTenantKeys = {};
|
|
67
|
+
for (const [v, k] of Object.entries(resolvedTenantKeysRaw)) {
|
|
68
|
+
resolvedTenantKeys[v] = Buffer.from(k);
|
|
69
|
+
}
|
|
70
|
+
(0, security_1.registerKeysForZeroization)(resolvedTenantKeys, tId);
|
|
61
71
|
const tActiveVersion = tenantConfig.kms?.activeKeyVersion || '1';
|
|
62
72
|
const tActiveKey = resolvedTenantKeys[tActiveVersion];
|
|
63
73
|
if (!tActiveKey) {
|
|
64
|
-
throw new Error(`Vollcrypt Security: Active key version "${tActiveVersion}" not found for tenantId "${
|
|
74
|
+
throw new Error(`Vollcrypt Security: Active key version "${tActiveVersion}" not found for tenantId "${tId}".`);
|
|
65
75
|
}
|
|
66
76
|
for (const [ver, keyBuf] of Object.entries(resolvedTenantKeys)) {
|
|
67
|
-
(0, security_1.setCachedKey)(
|
|
77
|
+
(0, security_1.setCachedKey)(tId, ver, keyBuf);
|
|
68
78
|
}
|
|
69
79
|
return { keys: resolvedTenantKeys, activeKey: tActiveKey, activeVersion: tActiveVersion };
|
|
70
80
|
};
|
package/dist/prisma.js
CHANGED
|
@@ -65,6 +65,11 @@ async function resolveKeys(options) {
|
|
|
65
65
|
function encryptValue(val, key, version) {
|
|
66
66
|
if (val === null || val === undefined)
|
|
67
67
|
return val;
|
|
68
|
+
const context = security_1.dbGuardContextStore.getStore();
|
|
69
|
+
const tId = context?.tenantId || 'global';
|
|
70
|
+
if (key.every(b => b === 0) || (0, security_1.getFailClosedStatus)(tId)) {
|
|
71
|
+
throw new Error(`Vollcrypt Security: Fail-Closed mode is active for tenant "${tId}". Encryption blocked.`);
|
|
72
|
+
}
|
|
68
73
|
const plaintext = typeof val === 'string' ? val : JSON.stringify(val);
|
|
69
74
|
const plaintextBuf = Buffer.from(plaintext, 'utf8');
|
|
70
75
|
const encrypted = security_1.CRYPTO_ALGORITHMS['1'].encrypt(plaintextBuf, key);
|
|
@@ -85,6 +90,9 @@ function decryptValue(stored, keys) {
|
|
|
85
90
|
if (!key) {
|
|
86
91
|
throw new Error(`Decryption key version "${version}" not found in registered keys`);
|
|
87
92
|
}
|
|
93
|
+
if (key.every(b => b === 0)) {
|
|
94
|
+
throw new Error(`Vollcrypt Security: Decryption blocked. Key version "${version}" is zeroized due to a Fail-Closed event.`);
|
|
95
|
+
}
|
|
88
96
|
try {
|
|
89
97
|
const encryptedBuf = Buffer.from(base64Data, 'base64');
|
|
90
98
|
const decryptor = security_1.CRYPTO_ALGORITHMS[algoId];
|
|
@@ -169,10 +177,13 @@ const prismaDbGuard = (options, resolvedKeys) => {
|
|
|
169
177
|
if (!keys) {
|
|
170
178
|
if (options.key) {
|
|
171
179
|
if (Buffer.isBuffer(options.key)) {
|
|
172
|
-
keys = { '1': options.key };
|
|
180
|
+
keys = { '1': Buffer.from(options.key) };
|
|
173
181
|
}
|
|
174
182
|
else {
|
|
175
|
-
keys = {
|
|
183
|
+
keys = {};
|
|
184
|
+
for (const [v, k] of Object.entries(options.key)) {
|
|
185
|
+
keys[v] = Buffer.from(k);
|
|
186
|
+
}
|
|
176
187
|
}
|
|
177
188
|
}
|
|
178
189
|
else if (options.kms || options.multiTenant) {
|
|
@@ -183,6 +194,11 @@ const prismaDbGuard = (options, resolvedKeys) => {
|
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
if (keys) {
|
|
197
|
+
const clonedKeys = {};
|
|
198
|
+
for (const [v, k] of Object.entries(keys)) {
|
|
199
|
+
clonedKeys[v] = Buffer.from(k);
|
|
200
|
+
}
|
|
201
|
+
keys = clonedKeys;
|
|
186
202
|
(0, security_1.registerKeysForZeroization)(keys);
|
|
187
203
|
}
|
|
188
204
|
const activeVersion = options.kms?.activeKeyVersion || '1';
|
|
@@ -194,41 +210,49 @@ const prismaDbGuard = (options, resolvedKeys) => {
|
|
|
194
210
|
return { keys: { '1': bgKey }, activeKey: bgKey, activeVersion: '1' };
|
|
195
211
|
}
|
|
196
212
|
}
|
|
197
|
-
if (
|
|
213
|
+
if (options.multiTenant && !tenantId) {
|
|
214
|
+
throw new Error("Vollcrypt Security: tenantId must be provided in multi-tenant mode.");
|
|
215
|
+
}
|
|
216
|
+
if (!options.multiTenant) {
|
|
198
217
|
if (!keys || !activeKey) {
|
|
199
218
|
throw new Error("Vollcrypt Security: Global keys are not resolved.");
|
|
200
219
|
}
|
|
201
220
|
return { keys, activeKey, activeVersion };
|
|
202
221
|
}
|
|
222
|
+
const tId = tenantId;
|
|
203
223
|
// Check Secure TTL Cache
|
|
204
|
-
const cachedActiveKey = (0, security_1.getCachedKey)(
|
|
224
|
+
const cachedActiveKey = (0, security_1.getCachedKey)(tId, activeVersion);
|
|
205
225
|
if (cachedActiveKey) {
|
|
206
226
|
return { keys: { [activeVersion]: cachedActiveKey }, activeKey: cachedActiveKey, activeVersion };
|
|
207
227
|
}
|
|
208
228
|
// Cache miss: resolve configuration
|
|
209
229
|
let tenantConfig;
|
|
210
230
|
if (options.multiTenant.tenants) {
|
|
211
|
-
tenantConfig = options.multiTenant.tenants[
|
|
231
|
+
tenantConfig = options.multiTenant.tenants[tId];
|
|
212
232
|
}
|
|
213
233
|
else if (options.multiTenant.getTenantConfig) {
|
|
214
|
-
tenantConfig = await options.multiTenant.getTenantConfig(
|
|
234
|
+
tenantConfig = await options.multiTenant.getTenantConfig(tId);
|
|
215
235
|
}
|
|
216
236
|
if (!tenantConfig) {
|
|
217
|
-
throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${
|
|
237
|
+
throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tId}".`);
|
|
218
238
|
}
|
|
219
|
-
const
|
|
239
|
+
const resolvedTenantKeysRaw = await resolveKeys({
|
|
220
240
|
...options,
|
|
221
241
|
key: tenantConfig.key,
|
|
222
242
|
kms: tenantConfig.kms
|
|
223
243
|
});
|
|
224
|
-
|
|
244
|
+
const resolvedTenantKeys = {};
|
|
245
|
+
for (const [v, k] of Object.entries(resolvedTenantKeysRaw)) {
|
|
246
|
+
resolvedTenantKeys[v] = Buffer.from(k);
|
|
247
|
+
}
|
|
248
|
+
(0, security_1.registerKeysForZeroization)(resolvedTenantKeys, tId);
|
|
225
249
|
const tActiveVersion = tenantConfig.kms?.activeKeyVersion || '1';
|
|
226
250
|
const tActiveKey = resolvedTenantKeys[tActiveVersion];
|
|
227
251
|
if (!tActiveKey) {
|
|
228
|
-
throw new Error(`Vollcrypt Security: Active key version "${tActiveVersion}" not found for tenantId "${
|
|
252
|
+
throw new Error(`Vollcrypt Security: Active key version "${tActiveVersion}" not found for tenantId "${tId}".`);
|
|
229
253
|
}
|
|
230
254
|
for (const [ver, keyBuf] of Object.entries(resolvedTenantKeys)) {
|
|
231
|
-
(0, security_1.setCachedKey)(
|
|
255
|
+
(0, security_1.setCachedKey)(tId, ver, keyBuf);
|
|
232
256
|
}
|
|
233
257
|
return { keys: resolvedTenantKeys, activeKey: tActiveKey, activeVersion: tActiveVersion };
|
|
234
258
|
};
|
package/dist/provenance.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{
|
|
11
11
|
"name": "dist/blind-index.js",
|
|
12
12
|
"digest": {
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "83364c9d2a86d43f775f15537ee10bdfb8e06c852adceb5b3c7358bcfc412907"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
{
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
{
|
|
47
47
|
"name": "dist/drivers.js",
|
|
48
48
|
"digest": {
|
|
49
|
-
"sha256": "
|
|
49
|
+
"sha256": "4b418fae086ee440bd1c9fe99328c86923ab83467ecfb70fd9bb4ebee324e7f6"
|
|
50
50
|
}
|
|
51
51
|
},
|
|
52
52
|
{
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
{
|
|
59
59
|
"name": "dist/drizzle.js",
|
|
60
60
|
"digest": {
|
|
61
|
-
"sha256": "
|
|
61
|
+
"sha256": "c7b3210b0cca48f2b45c5b789cea13f55f14fc2c620e155d15987b9b8e434f4a"
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
64
|
{
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
{
|
|
83
83
|
"name": "dist/kms.js",
|
|
84
84
|
"digest": {
|
|
85
|
-
"sha256": "
|
|
85
|
+
"sha256": "5c748722412b465c01cebd70e2242836f8f0c10f4303189548061f88d67eb4e4"
|
|
86
86
|
}
|
|
87
87
|
},
|
|
88
88
|
{
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
{
|
|
95
95
|
"name": "dist/mongoose.js",
|
|
96
96
|
"digest": {
|
|
97
|
-
"sha256": "
|
|
97
|
+
"sha256": "a2ff33065a1c52707bdc3b7b1b49e79c5878e7edc547e219779426d3376a8f5b"
|
|
98
98
|
}
|
|
99
99
|
},
|
|
100
100
|
{
|
|
@@ -106,19 +106,19 @@
|
|
|
106
106
|
{
|
|
107
107
|
"name": "dist/prisma.js",
|
|
108
108
|
"digest": {
|
|
109
|
-
"sha256": "
|
|
109
|
+
"sha256": "dc5d221dfc6e2f790fd13abc1408e98e4d8a3a1f61ee876cc691963d5517395a"
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"name": "dist/security.d.ts",
|
|
114
114
|
"digest": {
|
|
115
|
-
"sha256": "
|
|
115
|
+
"sha256": "6fed9ab5d0e7d940fd8e0e65f3e0a6be6581fbeddb82a6ad9706397efa4ec2b2"
|
|
116
116
|
}
|
|
117
117
|
},
|
|
118
118
|
{
|
|
119
119
|
"name": "dist/security.js",
|
|
120
120
|
"digest": {
|
|
121
|
-
"sha256": "
|
|
121
|
+
"sha256": "a7323f84eaa90838dcaba18f82cba8c48d6fdb708715786cae1c60bdaeb0050d"
|
|
122
122
|
}
|
|
123
123
|
},
|
|
124
124
|
{
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
{
|
|
131
131
|
"name": "dist/typeorm.js",
|
|
132
132
|
"digest": {
|
|
133
|
-
"sha256": "
|
|
133
|
+
"sha256": "d88d0aa0eff62675e732c5fd2f7c7885d601d5234729687aecbd5b1282e6a7bd"
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
],
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"externalParameters": {
|
|
142
142
|
"packageJson": {
|
|
143
143
|
"name": "@vollcrypt/db-guard",
|
|
144
|
-
"version": "0.
|
|
144
|
+
"version": "0.9.0",
|
|
145
145
|
"scripts": {
|
|
146
146
|
"build": "tsc && node scripts/generate-sbom.js",
|
|
147
147
|
"test": "node --import tsx --test tests/*.test.ts"
|
|
@@ -170,6 +170,22 @@
|
|
|
170
170
|
"uri": "pkg:npm/typeorm@0.3.0",
|
|
171
171
|
"digest": {}
|
|
172
172
|
},
|
|
173
|
+
{
|
|
174
|
+
"uri": "pkg:npm/@aws-sdk/client-kms@3.0.0",
|
|
175
|
+
"digest": {}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"uri": "pkg:npm/@google-cloud/kms@3.0.0",
|
|
179
|
+
"digest": {}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"uri": "pkg:npm/node-vault@0.9.0",
|
|
183
|
+
"digest": {}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"uri": "pkg:npm/pkcs11js@1.0.0",
|
|
187
|
+
"digest": {}
|
|
188
|
+
},
|
|
173
189
|
{
|
|
174
190
|
"uri": "pkg:rust/aes-gcm",
|
|
175
191
|
"digest": {}
|
|
@@ -225,9 +241,9 @@
|
|
|
225
241
|
"id": "https://vollcrypt.dev/builders/local-hermetic-env"
|
|
226
242
|
},
|
|
227
243
|
"metadata": {
|
|
228
|
-
"invocationId": "
|
|
229
|
-
"startedOn": "2026-
|
|
230
|
-
"finishedOn": "2026-
|
|
244
|
+
"invocationId": "394b345f273d241cc656eb0efdf34f57",
|
|
245
|
+
"startedOn": "2026-07-08T14:31:16.765Z",
|
|
246
|
+
"finishedOn": "2026-07-08T14:31:16.767Z"
|
|
231
247
|
}
|
|
232
248
|
}
|
|
233
249
|
}
|
package/dist/provenance.json.sig
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
&�/�!/�[5_\4|�%�u����C>t}���Ɋ��kV[�3SI!�#� K�%#�cP��
|
package/dist/sbom.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.5",
|
|
4
|
-
"serialNumber": "urn:uuid:
|
|
4
|
+
"serialNumber": "urn:uuid:dc37a041-235d-478b-85b1-b33d761679d6",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
|
-
"timestamp": "2026-
|
|
7
|
+
"timestamp": "2026-07-08T14:31:16.765Z",
|
|
8
8
|
"tools": [
|
|
9
9
|
{
|
|
10
10
|
"vendor": "Vollcrypt",
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
}
|
|
14
14
|
],
|
|
15
15
|
"component": {
|
|
16
|
-
"bomRef": "pkg:npm/@vollcrypt/db-guard@0.
|
|
16
|
+
"bomRef": "pkg:npm/@vollcrypt/db-guard@0.9.0",
|
|
17
17
|
"type": "library",
|
|
18
18
|
"name": "@vollcrypt/db-guard",
|
|
19
|
-
"version": "0.
|
|
19
|
+
"version": "0.9.0",
|
|
20
20
|
"description": "Database field-level encryption integrations for Vollcrypt (Prisma, Mongoose, Drizzle, TypeORM)",
|
|
21
21
|
"hashes": [
|
|
22
22
|
{
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"alg": "SHA-256",
|
|
28
|
-
"content": "
|
|
28
|
+
"content": "83364c9d2a86d43f775f15537ee10bdfb8e06c852adceb5b3c7358bcfc412907"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"alg": "SHA-256",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"alg": "SHA-256",
|
|
52
|
-
"content": "
|
|
52
|
+
"content": "4b418fae086ee440bd1c9fe99328c86923ab83467ecfb70fd9bb4ebee324e7f6"
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"alg": "SHA-256",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
"alg": "SHA-256",
|
|
60
|
-
"content": "
|
|
60
|
+
"content": "c7b3210b0cca48f2b45c5b789cea13f55f14fc2c620e155d15987b9b8e434f4a"
|
|
61
61
|
},
|
|
62
62
|
{
|
|
63
63
|
"alg": "SHA-256",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"alg": "SHA-256",
|
|
76
|
-
"content": "
|
|
76
|
+
"content": "5c748722412b465c01cebd70e2242836f8f0c10f4303189548061f88d67eb4e4"
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"alg": "SHA-256",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
83
|
"alg": "SHA-256",
|
|
84
|
-
"content": "
|
|
84
|
+
"content": "a2ff33065a1c52707bdc3b7b1b49e79c5878e7edc547e219779426d3376a8f5b"
|
|
85
85
|
},
|
|
86
86
|
{
|
|
87
87
|
"alg": "SHA-256",
|
|
@@ -89,15 +89,15 @@
|
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"alg": "SHA-256",
|
|
92
|
-
"content": "
|
|
92
|
+
"content": "dc5d221dfc6e2f790fd13abc1408e98e4d8a3a1f61ee876cc691963d5517395a"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"alg": "SHA-256",
|
|
96
|
-
"content": "
|
|
96
|
+
"content": "6fed9ab5d0e7d940fd8e0e65f3e0a6be6581fbeddb82a6ad9706397efa4ec2b2"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"alg": "SHA-256",
|
|
100
|
-
"content": "
|
|
100
|
+
"content": "a7323f84eaa90838dcaba18f82cba8c48d6fdb708715786cae1c60bdaeb0050d"
|
|
101
101
|
},
|
|
102
102
|
{
|
|
103
103
|
"alg": "SHA-256",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"alg": "SHA-256",
|
|
108
|
-
"content": "
|
|
108
|
+
"content": "d88d0aa0eff62675e732c5fd2f7c7885d601d5234729687aecbd5b1282e6a7bd"
|
|
109
109
|
}
|
|
110
110
|
]
|
|
111
111
|
},
|
|
@@ -141,6 +141,30 @@
|
|
|
141
141
|
"version": ">=0.3.0",
|
|
142
142
|
"purl": "pkg:npm/typeorm@0.3.0"
|
|
143
143
|
},
|
|
144
|
+
{
|
|
145
|
+
"type": "library",
|
|
146
|
+
"name": "@aws-sdk/client-kms",
|
|
147
|
+
"version": ">=3.0.0",
|
|
148
|
+
"purl": "pkg:npm/@aws-sdk/client-kms@3.0.0"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"type": "library",
|
|
152
|
+
"name": "@google-cloud/kms",
|
|
153
|
+
"version": ">=3.0.0",
|
|
154
|
+
"purl": "pkg:npm/@google-cloud/kms@3.0.0"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"type": "library",
|
|
158
|
+
"name": "node-vault",
|
|
159
|
+
"version": ">=0.9.0",
|
|
160
|
+
"purl": "pkg:npm/node-vault@0.9.0"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"type": "library",
|
|
164
|
+
"name": "pkcs11js",
|
|
165
|
+
"version": ">=1.0.0",
|
|
166
|
+
"purl": "pkg:npm/pkcs11js@1.0.0"
|
|
167
|
+
},
|
|
144
168
|
{
|
|
145
169
|
"type": "library",
|
|
146
170
|
"name": "rust-crate:aes-gcm",
|
package/dist/sbom.json.sig
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
���ǂDm�`��Z>s=��"Y&��ؐ7�#b"h��S��W��
|
|
2
|
+
"�ޚ���*P?��
|
package/dist/security.d.ts
CHANGED
|
@@ -46,11 +46,11 @@ export declare function activateBreakGlass(signatures: {
|
|
|
46
46
|
signature: string;
|
|
47
47
|
timestamp: number;
|
|
48
48
|
}[], emergencyBackupKey: Buffer): void;
|
|
49
|
-
export declare function registerKeysForZeroization(keys: Record<string, Buffer
|
|
50
|
-
export declare function triggerFailClosed(onFailClosedCallback?: () => void): void;
|
|
49
|
+
export declare function registerKeysForZeroization(keys: Record<string, Buffer>, tenantId?: string): void;
|
|
50
|
+
export declare function triggerFailClosed(onFailClosedCallback?: () => void, tenantId?: string): void;
|
|
51
51
|
export declare function checkRateLimit(options?: RateLimiterOptions): void;
|
|
52
52
|
export declare function checkPageSize(count: number, options?: RateLimiterOptions): 'ok' | 'warn' | 'bypass' | 'error';
|
|
53
|
-
export declare function getFailClosedStatus(): boolean;
|
|
53
|
+
export declare function getFailClosedStatus(tenantId?: string): boolean;
|
|
54
54
|
export declare function resetFailClosedStatusForTesting(): void;
|
|
55
55
|
export interface AuditLogEntry {
|
|
56
56
|
timestamp: string;
|
package/dist/security.js
CHANGED
|
@@ -209,15 +209,14 @@ function maskValue(val, rule) {
|
|
|
209
209
|
return '***';
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
-
let decryptCount = 0;
|
|
213
|
-
let windowStart = Date.now();
|
|
214
|
-
let isFailClosed = false;
|
|
215
|
-
const globalKeysToZeroize = [];
|
|
216
212
|
// Ephemeral Master Key generated randomly on startup
|
|
217
213
|
let ephemeralMasterKey = crypto.randomBytes(32);
|
|
214
|
+
const tenantFailClosed = new Map();
|
|
215
|
+
const tenantKeys = new Map();
|
|
216
|
+
const tenantRateLimitStates = new Map();
|
|
218
217
|
const secureKeyCache = new Map();
|
|
219
218
|
function getCachedKey(tenantId, version) {
|
|
220
|
-
const cacheKey =
|
|
219
|
+
const cacheKey = JSON.stringify([tenantId || 'global', version]);
|
|
221
220
|
const entry = secureKeyCache.get(cacheKey);
|
|
222
221
|
if (!entry)
|
|
223
222
|
return undefined;
|
|
@@ -234,7 +233,7 @@ function getCachedKey(tenantId, version) {
|
|
|
234
233
|
}
|
|
235
234
|
}
|
|
236
235
|
function setCachedKey(tenantId, version, plaintextKey, ttlMs = 120000) {
|
|
237
|
-
const cacheKey =
|
|
236
|
+
const cacheKey = JSON.stringify([tenantId || 'global', version]);
|
|
238
237
|
const existing = secureKeyCache.get(cacheKey);
|
|
239
238
|
if (existing) {
|
|
240
239
|
existing.wrappedKey.fill(0);
|
|
@@ -327,27 +326,58 @@ function activateBreakGlass(signatures, emergencyBackupKey) {
|
|
|
327
326
|
isBreakGlassActiveFlag = true;
|
|
328
327
|
logDecryption('SYSTEM', 'BREAK_GLASS_ACTIVATED', undefined);
|
|
329
328
|
}
|
|
330
|
-
function registerKeysForZeroization(keys) {
|
|
331
|
-
|
|
332
|
-
|
|
329
|
+
function registerKeysForZeroization(keys, tenantId) {
|
|
330
|
+
const tId = tenantId || 'global';
|
|
331
|
+
let list = tenantKeys.get(tId);
|
|
332
|
+
if (!list) {
|
|
333
|
+
list = [];
|
|
334
|
+
tenantKeys.set(tId, list);
|
|
335
|
+
}
|
|
336
|
+
if (!list.includes(keys)) {
|
|
337
|
+
list.push(keys);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function triggerFailClosed(onFailClosedCallback, tenantId) {
|
|
341
|
+
const tId = tenantId || exports.dbGuardContextStore.getStore()?.tenantId || 'global';
|
|
342
|
+
tenantFailClosed.set(tId, true);
|
|
343
|
+
// Zeroize all registered keys immediately in memory for this tenant
|
|
344
|
+
const list = tenantKeys.get(tId);
|
|
345
|
+
if (list && list.length > 0) {
|
|
346
|
+
for (const keyMap of list) {
|
|
347
|
+
for (const key of Object.values(keyMap)) {
|
|
348
|
+
key.fill(0);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
333
351
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
352
|
+
else {
|
|
353
|
+
// Fallback to global keys if no tenant-specific keys are registered
|
|
354
|
+
const globalList = tenantKeys.get('global');
|
|
355
|
+
if (globalList) {
|
|
356
|
+
for (const keyMap of globalList) {
|
|
357
|
+
for (const key of Object.values(keyMap)) {
|
|
358
|
+
key.fill(0);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
341
361
|
}
|
|
342
362
|
}
|
|
343
363
|
// Zeroize cache and ephemeral master key
|
|
344
|
-
for (const entry of secureKeyCache.
|
|
345
|
-
|
|
364
|
+
for (const [cacheKey, entry] of secureKeyCache.entries()) {
|
|
365
|
+
try {
|
|
366
|
+
const parsed = JSON.parse(cacheKey);
|
|
367
|
+
if (Array.isArray(parsed) && parsed[0] === tId) {
|
|
368
|
+
entry.wrappedKey.fill(0);
|
|
369
|
+
secureKeyCache.delete(cacheKey);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
// fallback
|
|
374
|
+
}
|
|
346
375
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
376
|
+
if (tId === 'global') {
|
|
377
|
+
ephemeralMasterKey.fill(0);
|
|
378
|
+
if (breakGlassEmergencyKey) {
|
|
379
|
+
breakGlassEmergencyKey.fill(0);
|
|
380
|
+
}
|
|
351
381
|
}
|
|
352
382
|
if (onFailClosedCallback) {
|
|
353
383
|
try {
|
|
@@ -357,59 +387,45 @@ function triggerFailClosed(onFailClosedCallback) {
|
|
|
357
387
|
// prevent user callback crash from blocking zeroization
|
|
358
388
|
}
|
|
359
389
|
}
|
|
360
|
-
throw new Error(
|
|
390
|
+
throw new Error(`Vollcrypt Security: Decryption rate limit exceeded. Fail-Closed mode triggered for tenant "${tId}". Keys zeroized.`);
|
|
361
391
|
}
|
|
362
392
|
function checkRateLimit(options) {
|
|
363
|
-
if (isFailClosed) {
|
|
364
|
-
throw new Error('Vollcrypt Security: Fail-Closed mode is active. Decryption blocked.');
|
|
365
|
-
}
|
|
366
393
|
const context = exports.dbGuardContextStore.getStore();
|
|
394
|
+
const tId = context?.tenantId || 'global';
|
|
395
|
+
if (tenantFailClosed.get(tId)) {
|
|
396
|
+
throw new Error(`Vollcrypt Security: Fail-Closed mode is active for tenant "${tId}". Decryption blocked.`);
|
|
397
|
+
}
|
|
367
398
|
if (context?.bypassRateLimit) {
|
|
368
399
|
return; // Rate limit check bypassed for this request context
|
|
369
400
|
}
|
|
370
401
|
const limit = context?.maxDecryptionsPerSecond || options?.maxDecryptionsPerSecond || 500;
|
|
371
402
|
const mode = context?.rateLimiterMode || options?.mode || 'fail_closed';
|
|
372
403
|
const now = Date.now();
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
404
|
+
let state = tenantRateLimitStates.get(tId);
|
|
405
|
+
if (!state) {
|
|
406
|
+
state = { decryptCount: 0, windowStart: now };
|
|
407
|
+
tenantRateLimitStates.set(tId, state);
|
|
408
|
+
}
|
|
409
|
+
if (now - state.windowStart > 1000) {
|
|
410
|
+
state.decryptCount = 0;
|
|
411
|
+
state.windowStart = now;
|
|
412
|
+
}
|
|
413
|
+
state.decryptCount++;
|
|
414
|
+
if (state.decryptCount > limit) {
|
|
415
|
+
if (mode === 'fail_closed') {
|
|
416
|
+
triggerFailClosed(options?.onFailClosed, tId);
|
|
377
417
|
}
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
context.windowStart = now;
|
|
381
|
-
}
|
|
382
|
-
context.decryptCount++;
|
|
383
|
-
if (context.decryptCount > limit) {
|
|
384
|
-
if (mode === 'fail_closed') {
|
|
385
|
-
triggerFailClosed(options?.onFailClosed);
|
|
386
|
-
}
|
|
387
|
-
else if (mode === 'warn') {
|
|
388
|
-
console.warn(`Vollcrypt Warning: Decryption rate limit exceeded. ${context.decryptCount} decryptions in the current window (limit: ${limit}).`);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
if (now - windowStart > 1000) {
|
|
394
|
-
decryptCount = 0;
|
|
395
|
-
windowStart = now;
|
|
396
|
-
}
|
|
397
|
-
decryptCount++;
|
|
398
|
-
if (decryptCount > limit) {
|
|
399
|
-
if (mode === 'fail_closed') {
|
|
400
|
-
triggerFailClosed(options?.onFailClosed);
|
|
401
|
-
}
|
|
402
|
-
else if (mode === 'warn') {
|
|
403
|
-
console.warn(`Vollcrypt Warning: Decryption rate limit exceeded. ${decryptCount} decryptions in the current window (limit: ${limit}).`);
|
|
404
|
-
}
|
|
418
|
+
else if (mode === 'warn') {
|
|
419
|
+
console.warn(`Vollcrypt Warning: Decryption rate limit exceeded for tenant "${tId}". ${state.decryptCount} decryptions in the current window (limit: ${limit}).`);
|
|
405
420
|
}
|
|
406
421
|
}
|
|
407
422
|
}
|
|
408
423
|
function checkPageSize(count, options) {
|
|
409
|
-
if (isFailClosed) {
|
|
410
|
-
throw new Error('Vollcrypt Security: Fail-Closed mode is active. Decryption blocked.');
|
|
411
|
-
}
|
|
412
424
|
const context = exports.dbGuardContextStore.getStore();
|
|
425
|
+
const tId = context?.tenantId || 'global';
|
|
426
|
+
if (tenantFailClosed.get(tId)) {
|
|
427
|
+
throw new Error(`Vollcrypt Security: Fail-Closed mode is active for tenant "${tId}". Decryption blocked.`);
|
|
428
|
+
}
|
|
413
429
|
const maxPageSize = context?.maxPageSize !== undefined
|
|
414
430
|
? context.maxPageSize
|
|
415
431
|
: (options?.maxPageSize !== undefined ? options.maxPageSize : 250);
|
|
@@ -430,17 +446,19 @@ function checkPageSize(count, options) {
|
|
|
430
446
|
}
|
|
431
447
|
return 'ok';
|
|
432
448
|
}
|
|
433
|
-
function getFailClosedStatus() {
|
|
434
|
-
|
|
449
|
+
function getFailClosedStatus(tenantId) {
|
|
450
|
+
const tId = tenantId || exports.dbGuardContextStore.getStore()?.tenantId || 'global';
|
|
451
|
+
return tenantFailClosed.get(tId) || false;
|
|
435
452
|
}
|
|
436
453
|
function resetFailClosedStatusForTesting() {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
454
|
+
tenantFailClosed.clear();
|
|
455
|
+
tenantRateLimitStates.clear();
|
|
456
|
+
tenantKeys.clear();
|
|
440
457
|
}
|
|
441
458
|
let lastLogHash = '0'.repeat(64);
|
|
442
459
|
let auditLogPath;
|
|
443
460
|
let onAuditLogCallback;
|
|
461
|
+
let auditWriteQueue = Promise.resolve();
|
|
444
462
|
function configureAuditLogger(options) {
|
|
445
463
|
if (options?.path) {
|
|
446
464
|
auditLogPath = options.path;
|
|
@@ -470,6 +488,7 @@ function resetAuditLoggerForTesting() {
|
|
|
470
488
|
lastLogHash = '0'.repeat(64);
|
|
471
489
|
auditLogPath = undefined;
|
|
472
490
|
onAuditLogCallback = undefined;
|
|
491
|
+
auditWriteQueue = Promise.resolve();
|
|
473
492
|
}
|
|
474
493
|
function logDecryption(model, field, recordId) {
|
|
475
494
|
const context = exports.dbGuardContextStore.getStore();
|
|
@@ -497,12 +516,11 @@ function logDecryption(model, field, recordId) {
|
|
|
497
516
|
}
|
|
498
517
|
}
|
|
499
518
|
if (auditLogPath) {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
}
|
|
519
|
+
const line = JSON.stringify(fullEntry) + '\n';
|
|
520
|
+
const currentPath = auditLogPath;
|
|
521
|
+
auditWriteQueue = auditWriteQueue.then(() => {
|
|
522
|
+
return fs.promises.appendFile(currentPath, line, 'utf8').catch(() => { });
|
|
523
|
+
});
|
|
506
524
|
}
|
|
507
525
|
}
|
|
508
526
|
function decryptWithSecurity(stored, decryptRawFn, modelName, fieldName, recordId, options) {
|
|
@@ -543,7 +561,8 @@ function decryptWithSecurity(stored, decryptRawFn, modelName, fieldName, recordI
|
|
|
543
561
|
return result;
|
|
544
562
|
}
|
|
545
563
|
exports.VERSION_ALGORITHMS = {
|
|
546
|
-
'1': '1'
|
|
564
|
+
'1': '1',
|
|
565
|
+
'2': '1'
|
|
547
566
|
};
|
|
548
567
|
exports.CRYPTO_ALGORITHMS = {
|
|
549
568
|
'1': {
|
|
@@ -557,12 +576,16 @@ function parseCiphertext(stored) {
|
|
|
557
576
|
const content = stored.slice('VOLLVALT:'.length);
|
|
558
577
|
if (content.startsWith('v')) {
|
|
559
578
|
const colon = content.indexOf(':');
|
|
560
|
-
if (colon === -1)
|
|
561
|
-
|
|
579
|
+
if (colon === -1) {
|
|
580
|
+
throw new Error("Vollcrypt Security: Malformed ciphertext format.");
|
|
581
|
+
}
|
|
562
582
|
const versionPart = content.slice(1, colon);
|
|
563
583
|
const base64Part = content.slice(colon + 1);
|
|
564
|
-
const algoId = exports.VERSION_ALGORITHMS[versionPart]
|
|
584
|
+
const algoId = exports.VERSION_ALGORITHMS[versionPart];
|
|
585
|
+
if (!algoId) {
|
|
586
|
+
throw new Error(`Vollcrypt Security: Deprecated or unsupported encryption version "v${versionPart}".`);
|
|
587
|
+
}
|
|
565
588
|
return { algoId, version: versionPart, base64Data: base64Part };
|
|
566
589
|
}
|
|
567
|
-
|
|
590
|
+
throw new Error("Vollcrypt Security: Legacy unversioned ciphertexts are deprecated and unsupported.");
|
|
568
591
|
}
|
package/dist/typeorm.js
CHANGED
|
@@ -43,11 +43,14 @@ function getKeys(options) {
|
|
|
43
43
|
let keys;
|
|
44
44
|
let activeVersion;
|
|
45
45
|
if (Buffer.isBuffer(options.key)) {
|
|
46
|
-
keys = { '1': options.key };
|
|
46
|
+
keys = { '1': Buffer.from(options.key) };
|
|
47
47
|
activeVersion = '1';
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
|
-
keys =
|
|
50
|
+
keys = {};
|
|
51
|
+
for (const [v, k] of Object.entries(options.key)) {
|
|
52
|
+
keys[v] = Buffer.from(k);
|
|
53
|
+
}
|
|
51
54
|
activeVersion = options.activeKeyVersion || Object.keys(keys)[0];
|
|
52
55
|
}
|
|
53
56
|
return { keys, activeVersion };
|
package/package.json
CHANGED
|
@@ -1,50 +1,80 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@vollcrypt/db-guard",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Database field-level encryption integrations for Vollcrypt (Prisma, Mongoose, Drizzle, TypeORM)",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"vollcrypt-db-guard": "dist/cli.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist",
|
|
12
|
-
"README.md",
|
|
13
|
-
"compliance-config.json"
|
|
14
|
-
],
|
|
15
|
-
"publishConfig": {
|
|
16
|
-
"access": "public"
|
|
17
|
-
},
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/BeratVural/vollcrypt.git",
|
|
21
|
-
"directory": "db-guard/node"
|
|
22
|
-
},
|
|
23
|
-
"author": "Berat Vural <berat.vural.tr@gmail.com>",
|
|
24
|
-
"license": "GPL-3.0-only OR LicenseRef-Commercial",
|
|
25
|
-
"bugs": {
|
|
26
|
-
"url": "https://github.com/BeratVural/vollcrypt/issues"
|
|
27
|
-
},
|
|
28
|
-
"homepage": "https://github.com/BeratVural/vollcrypt/tree/main/db-guard/node#readme",
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsc && node scripts/generate-sbom.js",
|
|
31
|
-
"test": "node --import tsx --test tests/*.test.ts"
|
|
32
|
-
},
|
|
33
|
-
"dependencies": {},
|
|
34
|
-
"peerDependencies": {
|
|
35
|
-
"@prisma/client": ">=4.7.0",
|
|
36
|
-
"mongoose": ">=6.0.0",
|
|
37
|
-
"drizzle-orm": ">=0.28.0",
|
|
38
|
-
"typeorm": ">=0.3.0"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@vollcrypt/db-guard",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Database field-level encryption integrations for Vollcrypt (Prisma, Mongoose, Drizzle, TypeORM)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"vollcrypt-db-guard": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"compliance-config.json"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/BeratVural/vollcrypt.git",
|
|
21
|
+
"directory": "db-guard/node"
|
|
22
|
+
},
|
|
23
|
+
"author": "Berat Vural <berat.vural.tr@gmail.com>",
|
|
24
|
+
"license": "GPL-3.0-only OR LicenseRef-Commercial",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/BeratVural/vollcrypt/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/BeratVural/vollcrypt/tree/main/db-guard/node#readme",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc && node scripts/generate-sbom.js",
|
|
31
|
+
"test": "node --import tsx --test tests/*.test.ts"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@prisma/client": ">=4.7.0",
|
|
36
|
+
"mongoose": ">=6.0.0",
|
|
37
|
+
"drizzle-orm": ">=0.28.0",
|
|
38
|
+
"typeorm": ">=0.3.0",
|
|
39
|
+
"@aws-sdk/client-kms": ">=3.0.0",
|
|
40
|
+
"@google-cloud/kms": ">=3.0.0",
|
|
41
|
+
"node-vault": ">=0.9.0",
|
|
42
|
+
"pkcs11js": ">=1.0.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"@prisma/client": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"mongoose": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"drizzle-orm": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"typeorm": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@aws-sdk/client-kms": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"@google-cloud/kms": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"node-vault": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"pkcs11js": {
|
|
67
|
+
"optional": true
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"devDependencies": {
|
|
71
|
+
"@prisma/client": "^5.0.0",
|
|
72
|
+
"@types/node": "^20.11.0",
|
|
73
|
+
"mongoose": "^8.0.0",
|
|
74
|
+
"prisma": "^5.0.0",
|
|
75
|
+
"drizzle-orm": "^0.30.0",
|
|
76
|
+
"typeorm": "^0.3.20",
|
|
77
|
+
"tsx": "^4.7.0",
|
|
78
|
+
"typescript": "^5.5.2"
|
|
79
|
+
}
|
|
80
|
+
}
|