@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.
@@ -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
- // 2. Compute the final blind index using the derived column key
19
- const plaintextBuf = Buffer.from(plaintext, 'utf8');
20
- const blindIndex = (0, security_1.deriveHkdf)(derivedColumnKey, null, plaintextBuf, 32);
21
- // 3. RAM Security: Zeroize the derived key immediately (Anti-Core Dump)
22
- derivedColumnKey.fill(0);
23
- return blindIndex.toString('hex');
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
- keys = options.key;
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+(\w+)\s*\(([^)]+)\)/i);
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.trim());
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+(\w+)\s+SET\s+([\s\S]+?)(?:\s+WHERE|$)/i);
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(/(\w+)\s*=/);
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 = options.key;
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
- pkcs11.C_Initialize();
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
- keys = options.key;
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 (!tenantId || !options.multiTenant) {
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)(tenantId, activeVersion);
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[tenantId];
53
+ tenantConfig = multiTenant.tenants[tId];
48
54
  }
49
55
  else if (multiTenant.getTenantConfig) {
50
- tenantConfig = await multiTenant.getTenantConfig(tenantId);
56
+ tenantConfig = await multiTenant.getTenantConfig(tId);
51
57
  }
52
58
  if (!tenantConfig) {
53
- throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tenantId}".`);
59
+ throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tId}".`);
54
60
  }
55
- const resolvedTenantKeys = await (0, prisma_1.resolveKeys)({
61
+ const resolvedTenantKeysRaw = await (0, prisma_1.resolveKeys)({
56
62
  ...options,
57
63
  key: tenantConfig.key,
58
64
  kms: tenantConfig.kms
59
65
  });
60
- (0, security_1.registerKeysForZeroization)(resolvedTenantKeys);
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 "${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)(tenantId, ver, keyBuf);
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 = { ...options.key };
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 (!tenantId || !options.multiTenant) {
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)(tenantId, activeVersion);
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[tenantId];
231
+ tenantConfig = options.multiTenant.tenants[tId];
212
232
  }
213
233
  else if (options.multiTenant.getTenantConfig) {
214
- tenantConfig = await options.multiTenant.getTenantConfig(tenantId);
234
+ tenantConfig = await options.multiTenant.getTenantConfig(tId);
215
235
  }
216
236
  if (!tenantConfig) {
217
- throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tenantId}".`);
237
+ throw new Error(`Vollcrypt Security: Configuration not found for tenantId "${tId}".`);
218
238
  }
219
- const resolvedTenantKeys = await resolveKeys({
239
+ const resolvedTenantKeysRaw = await resolveKeys({
220
240
  ...options,
221
241
  key: tenantConfig.key,
222
242
  kms: tenantConfig.kms
223
243
  });
224
- (0, security_1.registerKeysForZeroization)(resolvedTenantKeys);
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 "${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)(tenantId, ver, keyBuf);
255
+ (0, security_1.setCachedKey)(tId, ver, keyBuf);
232
256
  }
233
257
  return { keys: resolvedTenantKeys, activeKey: tActiveKey, activeVersion: tActiveVersion };
234
258
  };
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "dist/blind-index.js",
12
12
  "digest": {
13
- "sha256": "fb28a7038cc818346aeb212ece2e5d4f1db4b72fd3ed9dda78a1a7f5744d7455"
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": "44623a5cf632c22a41b8196b809a3c00d50276bde28c2fbde13d6f4bef3177ce"
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": "aad49d4c9dd41f2e44175465da6ae7f4a7ae35337ce12f7b3a87113ec0f25424"
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": "4b1a32b35545e2e7944ebba3f84272b7421ffedd1cc3c7eb569e1a6b2fcc4a02"
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": "42b2177825713fa1c618b80af6cf7a5d9f4055399c8e95b67018a0410add2a3f"
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": "e2b7a8fa24c120f59ab48ebb508a18c01e3e24245e04c197b08ad0e327a2d9a8"
109
+ "sha256": "dc5d221dfc6e2f790fd13abc1408e98e4d8a3a1f61ee876cc691963d5517395a"
110
110
  }
111
111
  },
112
112
  {
113
113
  "name": "dist/security.d.ts",
114
114
  "digest": {
115
- "sha256": "554d9ba00f800470666ab66127079df889b8d717a9cf7b28ab252f0adad64304"
115
+ "sha256": "6fed9ab5d0e7d940fd8e0e65f3e0a6be6581fbeddb82a6ad9706397efa4ec2b2"
116
116
  }
117
117
  },
118
118
  {
119
119
  "name": "dist/security.js",
120
120
  "digest": {
121
- "sha256": "5e907c9f5ec9fb93ef07f6360d7f8572aa5a1ea1c0a32d0b10496477d0b8fd33"
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": "154827069a5496e043a9f69b3249bd5dddd62690783dfa8675d06d0f574ac76b"
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.1.2",
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": "19095e456ee2a685d43df5cc56999080",
229
- "startedOn": "2026-06-11T16:17:36.609Z",
230
- "finishedOn": "2026-06-11T16:17:36.610Z"
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
  }
@@ -1 +1 @@
1
- ?NENc0�HӰ�@�S�t��J�ɽ<(:38O �7X�q-du�Y�^~՚�8�z� Dž�
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:45a7c8f7-12f3-4e25-ab52-81be917b4646",
4
+ "serialNumber": "urn:uuid:dc37a041-235d-478b-85b1-b33d761679d6",
5
5
  "version": 1,
6
6
  "metadata": {
7
- "timestamp": "2026-06-11T16:17:36.609Z",
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.1.2",
16
+ "bomRef": "pkg:npm/@vollcrypt/db-guard@0.9.0",
17
17
  "type": "library",
18
18
  "name": "@vollcrypt/db-guard",
19
- "version": "0.1.2",
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": "fb28a7038cc818346aeb212ece2e5d4f1db4b72fd3ed9dda78a1a7f5744d7455"
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": "44623a5cf632c22a41b8196b809a3c00d50276bde28c2fbde13d6f4bef3177ce"
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": "aad49d4c9dd41f2e44175465da6ae7f4a7ae35337ce12f7b3a87113ec0f25424"
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": "4b1a32b35545e2e7944ebba3f84272b7421ffedd1cc3c7eb569e1a6b2fcc4a02"
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": "42b2177825713fa1c618b80af6cf7a5d9f4055399c8e95b67018a0410add2a3f"
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": "e2b7a8fa24c120f59ab48ebb508a18c01e3e24245e04c197b08ad0e327a2d9a8"
92
+ "content": "dc5d221dfc6e2f790fd13abc1408e98e4d8a3a1f61ee876cc691963d5517395a"
93
93
  },
94
94
  {
95
95
  "alg": "SHA-256",
96
- "content": "554d9ba00f800470666ab66127079df889b8d717a9cf7b28ab252f0adad64304"
96
+ "content": "6fed9ab5d0e7d940fd8e0e65f3e0a6be6581fbeddb82a6ad9706397efa4ec2b2"
97
97
  },
98
98
  {
99
99
  "alg": "SHA-256",
100
- "content": "5e907c9f5ec9fb93ef07f6360d7f8572aa5a1ea1c0a32d0b10496477d0b8fd33"
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": "154827069a5496e043a9f69b3249bd5dddd62690783dfa8675d06d0f574ac76b"
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",
@@ -1,2 +1,2 @@
1
- ��7gu
2
- �
1
+ ���ǂDm�`�� Z>s=��"Y&��ؐ7�#b"h��S��W��
2
+ "�ޚ���*P?��
@@ -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>): void;
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 = `${tenantId || 'global'}:${version}`;
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 = `${tenantId || 'global'}:${version}`;
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
- if (!globalKeysToZeroize.includes(keys)) {
332
- globalKeysToZeroize.push(keys);
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
- function triggerFailClosed(onFailClosedCallback) {
336
- isFailClosed = true;
337
- // Zeroize all registered keys immediately in memory
338
- for (const keyMap of globalKeysToZeroize) {
339
- for (const key of Object.values(keyMap)) {
340
- key.fill(0);
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.values()) {
345
- entry.wrappedKey.fill(0);
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
- secureKeyCache.clear();
348
- ephemeralMasterKey.fill(0);
349
- if (breakGlassEmergencyKey) {
350
- breakGlassEmergencyKey.fill(0);
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('Vollcrypt Security: Decryption rate limit exceeded. Fail-Closed mode triggered. Keys zeroized.');
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
- if (context) {
374
- if (context.windowStart === undefined || context.decryptCount === undefined) {
375
- context.windowStart = now;
376
- context.decryptCount = 0;
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 (now - context.windowStart > 1000) {
379
- context.decryptCount = 0;
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
- return isFailClosed;
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
- isFailClosed = false;
438
- decryptCount = 0;
439
- windowStart = Date.now();
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
- try {
501
- fs.appendFileSync(auditLogPath, JSON.stringify(fullEntry) + '\n', 'utf8');
502
- }
503
- catch {
504
- // prevent filesystem errors from throwing
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
- return null;
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] || '1';
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
- return null;
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 = options.key;
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.1.2",
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
- "devDependencies": {
41
- "@prisma/client": "^5.0.0",
42
- "@types/node": "^20.11.0",
43
- "mongoose": "^8.0.0",
44
- "prisma": "^5.0.0",
45
- "drizzle-orm": "^0.30.0",
46
- "typeorm": "^0.3.20",
47
- "tsx": "^4.7.0",
48
- "typescript": "^5.5.2"
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
+ }