@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/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
@@ -1,4 +1,4 @@
1
- import { InsertEvent, UpdateEvent } from 'typeorm';
1
+ import type { InsertEvent, UpdateEvent } from 'typeorm';
2
2
  import { RateLimiterOptions } from './security';
3
3
  export interface TypeOrmDbGuardOptions {
4
4
  key: Buffer | Record<string, Buffer>;
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 = [(0, typeorm_1.EventSubscriber)()];
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, blind_index_1.computeBlindIndex)(event.entity[field], options.blindIndexes.rootSalt, `${entityName}.${field}`);
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, prisma_1.encryptValue)(event.entity[field], activeKey, activeVersion);
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, blind_index_1.computeBlindIndex)(event.entity[field], options.blindIndexes.rootSalt, `${entityName}.${field}`);
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, prisma_1.encryptValue)(event.entity[field], activeKey, activeVersion);
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, prisma_1.decryptValue)(val, keys), entityName, field, entity.id || entity._id, options);
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.0",
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"