@liuhange/dsh-data-masking 2.0.2 → 2.0.3
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/lib/advancedMaskingExecutor.js +94 -0
- package/lib/algorithms/differentialPrivacyAlgorithm.js +24 -0
- package/lib/algorithms/fpeAlgorithm.js +102 -0
- package/lib/algorithms/hashAlgorithm.js +16 -0
- package/lib/algorithms/kAnonymityAlgorithm.js +65 -0
- package/lib/algorithms/types.js +2 -0
- package/lib/budgetTracker.js +24 -0
- package/lib/defaultAdvancedMaskingConfig.js +22 -0
- package/lib/maskingStrategyExecutor.js +26 -0
- package/lib/sensitiveFieldScanner.js +25 -0
- package/package.json +2 -4
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { FpeAlgorithm } from './algorithms/fpeAlgorithm.js';
|
|
2
|
+
import { KAnonymityAlgorithm } from './algorithms/kAnonymityAlgorithm.js';
|
|
3
|
+
import { DifferentialPrivacyAlgorithm } from './algorithms/differentialPrivacyAlgorithm.js';
|
|
4
|
+
import { HashAlgorithm } from './algorithms/hashAlgorithm.js';
|
|
5
|
+
import { BudgetTracker } from './budgetTracker.js';
|
|
6
|
+
export class AdvancedMaskingExecutor {
|
|
7
|
+
fpeAlgorithm = new FpeAlgorithm();
|
|
8
|
+
kAnonymityAlgorithm = new KAnonymityAlgorithm();
|
|
9
|
+
differentialPrivacyAlgorithm = new DifferentialPrivacyAlgorithm();
|
|
10
|
+
hashAlgorithm = new HashAlgorithm();
|
|
11
|
+
execute(data, algorithm, fieldName, config) {
|
|
12
|
+
const fieldConfig = fieldName ? config.fieldAlgorithms[fieldName] : undefined;
|
|
13
|
+
const resolvedAlgorithm = fieldConfig?.algorithm ?? algorithm;
|
|
14
|
+
switch (resolvedAlgorithm) {
|
|
15
|
+
case 'FPE':
|
|
16
|
+
return this.executeFpe(data, config);
|
|
17
|
+
case 'k-anonymity':
|
|
18
|
+
return this.executeKAnonymity(data, config);
|
|
19
|
+
case 'differential-privacy':
|
|
20
|
+
return this.executeDifferentialPrivacy(data, config);
|
|
21
|
+
case 'hash':
|
|
22
|
+
return this.executeHash(data, config);
|
|
23
|
+
default:
|
|
24
|
+
return this.executeHash(data, config);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
executeFpe(data, config) {
|
|
28
|
+
const key = this.resolveEnvVar(config.fpe.key);
|
|
29
|
+
const encrypted = this.fpeAlgorithm.encrypt(data, key);
|
|
30
|
+
const details = {
|
|
31
|
+
algorithm: 'FPE',
|
|
32
|
+
reversible: true,
|
|
33
|
+
};
|
|
34
|
+
return { maskedData: encrypted, algorithm: 'FPE', details };
|
|
35
|
+
}
|
|
36
|
+
executeKAnonymity(data, config) {
|
|
37
|
+
const kValue = config.kAnonymity.kValue;
|
|
38
|
+
const quasiIdentifiers = config.kAnonymity.quasiIdentifiers;
|
|
39
|
+
let records;
|
|
40
|
+
try {
|
|
41
|
+
records = JSON.parse(data);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
records = [{ value: data }];
|
|
45
|
+
}
|
|
46
|
+
const result = this.kAnonymityAlgorithm.anonymize(records, kValue, quasiIdentifiers);
|
|
47
|
+
const details = {
|
|
48
|
+
algorithm: 'k-anonymity',
|
|
49
|
+
kValue,
|
|
50
|
+
actualMinEquivalenceClass: result.actualMinEquivalenceClass,
|
|
51
|
+
suppressedCount: result.suppressedCount,
|
|
52
|
+
};
|
|
53
|
+
return { maskedData: JSON.stringify(result.anonymizedRecords), algorithm: 'k-anonymity', details };
|
|
54
|
+
}
|
|
55
|
+
executeDifferentialPrivacy(data, config) {
|
|
56
|
+
const epsilon = config.differentialPrivacy.epsilon;
|
|
57
|
+
const totalBudget = config.differentialPrivacy.totalBudget;
|
|
58
|
+
const sensitivity = config.differentialPrivacy.sensitivity;
|
|
59
|
+
const budgetTracker = new BudgetTracker(totalBudget);
|
|
60
|
+
const trueValue = Number(data);
|
|
61
|
+
if (isNaN(trueValue)) {
|
|
62
|
+
throw new Error('差分隐私要求输入为数字');
|
|
63
|
+
}
|
|
64
|
+
const noisyValue = this.differentialPrivacyAlgorithm.addNoise(trueValue, epsilon, sensitivity, budgetTracker, totalBudget);
|
|
65
|
+
const budgetState = budgetTracker.getState();
|
|
66
|
+
const details = {
|
|
67
|
+
algorithm: 'differential-privacy',
|
|
68
|
+
epsilon,
|
|
69
|
+
budgetConsumed: budgetState.consumed,
|
|
70
|
+
budgetRemaining: budgetState.remaining,
|
|
71
|
+
};
|
|
72
|
+
return { maskedData: String(noisyValue), algorithm: 'differential-privacy', details };
|
|
73
|
+
}
|
|
74
|
+
executeHash(data, config) {
|
|
75
|
+
const hashAlgo = config.hash.algorithm;
|
|
76
|
+
const salt = this.resolveEnvVar(config.hash.salt);
|
|
77
|
+
const result = this.hashAlgorithm.hash(data, hashAlgo, salt || undefined);
|
|
78
|
+
const details = {
|
|
79
|
+
algorithm: 'hash',
|
|
80
|
+
hashAlgorithm: hashAlgo,
|
|
81
|
+
salted: result.salted,
|
|
82
|
+
digestLength: result.digestLength,
|
|
83
|
+
};
|
|
84
|
+
return { maskedData: result.digest, algorithm: 'hash', details };
|
|
85
|
+
}
|
|
86
|
+
resolveEnvVar(value) {
|
|
87
|
+
if (value.startsWith('${') && value.endsWith('}')) {
|
|
88
|
+
const envVar = value.slice(2, -1);
|
|
89
|
+
return process.env[envVar] ?? '';
|
|
90
|
+
}
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=advancedMaskingExecutor.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
export class DifferentialPrivacyAlgorithm {
|
|
3
|
+
addNoise(trueValue, epsilon, sensitivity, budgetTracker, totalBudget) {
|
|
4
|
+
if (!budgetTracker.canConsume(epsilon, totalBudget)) {
|
|
5
|
+
throw new Error('隐私预算已耗尽,拒绝查询');
|
|
6
|
+
}
|
|
7
|
+
const scale = sensitivity / epsilon;
|
|
8
|
+
const noise = this.generateLaplaceNoise(scale);
|
|
9
|
+
budgetTracker.consume(epsilon);
|
|
10
|
+
return trueValue + noise;
|
|
11
|
+
}
|
|
12
|
+
generateLaplaceNoise(scale) {
|
|
13
|
+
const u = this.generateSecureUniform();
|
|
14
|
+
const adjustedU = u - 0.5;
|
|
15
|
+
return -scale * Math.sign(adjustedU) * Math.log(1 - 2 * Math.abs(adjustedU));
|
|
16
|
+
}
|
|
17
|
+
generateSecureUniform() {
|
|
18
|
+
const bytes = crypto.randomBytes(8);
|
|
19
|
+
const uint64 = bytes.readBigUInt64BE(0);
|
|
20
|
+
const maxUint64 = BigInt(2) ** BigInt(64) - BigInt(1);
|
|
21
|
+
return Number(uint64) / Number(maxUint64);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=differentialPrivacyAlgorithm.js.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
export class FpeAlgorithm {
|
|
3
|
+
encrypt(plaintext, key) {
|
|
4
|
+
if (!key) {
|
|
5
|
+
throw new Error('错误:FPE密钥未配置');
|
|
6
|
+
}
|
|
7
|
+
const chars = plaintext.split('');
|
|
8
|
+
const radix = 10;
|
|
9
|
+
const isNumeric = chars.every(c => /\d/.test(c));
|
|
10
|
+
if (!isNumeric) {
|
|
11
|
+
return this.encryptGeneric(plaintext, key);
|
|
12
|
+
}
|
|
13
|
+
const n = plaintext.length;
|
|
14
|
+
if (n < 2) {
|
|
15
|
+
return plaintext;
|
|
16
|
+
}
|
|
17
|
+
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
18
|
+
const rounds = 10;
|
|
19
|
+
const inputNums = chars.map(c => parseInt(c, 10));
|
|
20
|
+
for (let round = 0; round < rounds; round++) {
|
|
21
|
+
const roundKey = crypto.createHash('sha256').update(keyHash).update(Buffer.from([round])).digest();
|
|
22
|
+
for (let i = 0; i < n - 1; i++) {
|
|
23
|
+
const mod = radix ** (n - i - 1);
|
|
24
|
+
const prfInput = Buffer.alloc(4);
|
|
25
|
+
prfInput.writeUInt32BE(inputNums[i], 0);
|
|
26
|
+
const prf = crypto.createHmac('sha256', roundKey).update(prfInput).digest();
|
|
27
|
+
const prfNum = prf.readUInt32BE(0) % mod;
|
|
28
|
+
inputNums[i + 1] = (inputNums[i + 1] + prfNum) % radix;
|
|
29
|
+
}
|
|
30
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
31
|
+
const mod = radix ** (n - i - 1);
|
|
32
|
+
const prfInput = Buffer.alloc(4);
|
|
33
|
+
prfInput.writeUInt32BE(inputNums[i + 1], 0);
|
|
34
|
+
const prf = crypto.createHmac('sha256', roundKey).update(prfInput).digest();
|
|
35
|
+
const prfNum = prf.readUInt32BE(0) % mod;
|
|
36
|
+
inputNums[i] = (inputNums[i] + prfNum) % radix;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return inputNums.join('');
|
|
40
|
+
}
|
|
41
|
+
decrypt(ciphertext, key) {
|
|
42
|
+
if (!key) {
|
|
43
|
+
throw new Error('错误:FPE密钥未配置');
|
|
44
|
+
}
|
|
45
|
+
const chars = ciphertext.split('');
|
|
46
|
+
const radix = 10;
|
|
47
|
+
const isNumeric = chars.every(c => /\d/.test(c));
|
|
48
|
+
if (!isNumeric) {
|
|
49
|
+
return this.decryptGeneric(ciphertext, key);
|
|
50
|
+
}
|
|
51
|
+
const n = ciphertext.length;
|
|
52
|
+
if (n < 2) {
|
|
53
|
+
return ciphertext;
|
|
54
|
+
}
|
|
55
|
+
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
56
|
+
const rounds = 10;
|
|
57
|
+
const outputNums = chars.map(c => parseInt(c, 10));
|
|
58
|
+
for (let round = rounds - 1; round >= 0; round--) {
|
|
59
|
+
const roundKey = crypto.createHash('sha256').update(keyHash).update(Buffer.from([round])).digest();
|
|
60
|
+
for (let i = 0; i < n - 1; i++) {
|
|
61
|
+
const mod = radix ** (n - i - 1);
|
|
62
|
+
const prfInput = Buffer.alloc(4);
|
|
63
|
+
prfInput.writeUInt32BE(outputNums[i + 1], 0);
|
|
64
|
+
const prf = crypto.createHmac('sha256', roundKey).update(prfInput).digest();
|
|
65
|
+
const prfNum = prf.readUInt32BE(0) % mod;
|
|
66
|
+
outputNums[i] = ((outputNums[i] - prfNum) % radix + radix) % radix;
|
|
67
|
+
}
|
|
68
|
+
for (let i = n - 2; i >= 0; i--) {
|
|
69
|
+
const mod = radix ** (n - i - 1);
|
|
70
|
+
const prfInput = Buffer.alloc(4);
|
|
71
|
+
prfInput.writeUInt32BE(outputNums[i], 0);
|
|
72
|
+
const prf = crypto.createHmac('sha256', roundKey).update(prfInput).digest();
|
|
73
|
+
const prfNum = prf.readUInt32BE(0) % mod;
|
|
74
|
+
outputNums[i + 1] = ((outputNums[i + 1] - prfNum) % radix + radix) % radix;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return outputNums.join('');
|
|
78
|
+
}
|
|
79
|
+
encryptGeneric(plaintext, key) {
|
|
80
|
+
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
81
|
+
const result = [];
|
|
82
|
+
for (let i = 0; i < plaintext.length; i++) {
|
|
83
|
+
const charCode = plaintext.charCodeAt(i);
|
|
84
|
+
const prf = crypto.createHmac('sha256', keyHash).update(Buffer.from([i & 0xff])).digest();
|
|
85
|
+
const offset = prf[0] % 256;
|
|
86
|
+
result.push(String.fromCharCode((charCode + offset) % 256));
|
|
87
|
+
}
|
|
88
|
+
return result.join('');
|
|
89
|
+
}
|
|
90
|
+
decryptGeneric(ciphertext, key) {
|
|
91
|
+
const keyHash = crypto.createHash('sha256').update(key).digest();
|
|
92
|
+
const result = [];
|
|
93
|
+
for (let i = 0; i < ciphertext.length; i++) {
|
|
94
|
+
const charCode = ciphertext.charCodeAt(i);
|
|
95
|
+
const prf = crypto.createHmac('sha256', keyHash).update(Buffer.from([i & 0xff])).digest();
|
|
96
|
+
const offset = prf[0] % 256;
|
|
97
|
+
result.push(String.fromCharCode((charCode - offset + 256) % 256));
|
|
98
|
+
}
|
|
99
|
+
return result.join('');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=fpeAlgorithm.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import * as crypto from 'crypto';
|
|
2
|
+
export class HashAlgorithm {
|
|
3
|
+
hash(plaintext, hashAlgorithm, salt) {
|
|
4
|
+
const input = salt ? salt + plaintext : plaintext;
|
|
5
|
+
const algorithm = hashAlgorithm === 'SHA-512' ? 'SHA-512' : 'SHA-256';
|
|
6
|
+
const hash = crypto.createHash(algorithm);
|
|
7
|
+
hash.update(input, 'utf-8');
|
|
8
|
+
const digest = hash.digest('hex');
|
|
9
|
+
return {
|
|
10
|
+
digest,
|
|
11
|
+
salted: salt !== undefined && salt !== '',
|
|
12
|
+
digestLength: digest.length,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=hashAlgorithm.js.map
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export class KAnonymityAlgorithm {
|
|
2
|
+
anonymize(records, kValue, quasiIdentifiers) {
|
|
3
|
+
if (records.length === 0 || quasiIdentifiers.length === 0) {
|
|
4
|
+
return { anonymizedRecords: records, actualMinEquivalenceClass: 0, suppressedCount: 0 };
|
|
5
|
+
}
|
|
6
|
+
let workingRecords = [...records];
|
|
7
|
+
let generalized = true;
|
|
8
|
+
let generalizationLevel = 0;
|
|
9
|
+
while (generalized) {
|
|
10
|
+
generalized = false;
|
|
11
|
+
const groups = this.groupByQuasiIdentifiers(workingRecords, quasiIdentifiers);
|
|
12
|
+
const minGroupSize = Math.min(...groups.map(g => g.length));
|
|
13
|
+
if (minGroupSize >= kValue) {
|
|
14
|
+
const minClass = Math.min(...groups.map(g => g.length));
|
|
15
|
+
return { anonymizedRecords: workingRecords, actualMinEquivalenceClass: minClass, suppressedCount: 0 };
|
|
16
|
+
}
|
|
17
|
+
if (generalizationLevel < 3) {
|
|
18
|
+
workingRecords = this.generalize(workingRecords, quasiIdentifiers, generalizationLevel);
|
|
19
|
+
generalizationLevel++;
|
|
20
|
+
generalized = true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const groups = this.groupByQuasiIdentifiers(workingRecords, quasiIdentifiers);
|
|
24
|
+
const validGroups = groups.filter(g => g.length >= kValue);
|
|
25
|
+
const suppressedRecords = groups.filter(g => g.length < kValue).flat();
|
|
26
|
+
const anonymizedRecords = validGroups.flat();
|
|
27
|
+
const allGroups = [...validGroups, ...groups.filter(g => g.length < kValue)];
|
|
28
|
+
const actualMin = allGroups.length > 0 ? Math.min(...allGroups.map(g => g.length)) : 0;
|
|
29
|
+
return {
|
|
30
|
+
anonymizedRecords,
|
|
31
|
+
actualMinEquivalenceClass: actualMin,
|
|
32
|
+
suppressedCount: suppressedRecords.length,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
groupByQuasiIdentifiers(records, quasiIdentifiers) {
|
|
36
|
+
const groups = new Map();
|
|
37
|
+
for (const record of records) {
|
|
38
|
+
const key = quasiIdentifiers.map(qi => String(record[qi] ?? '')).join('|');
|
|
39
|
+
const group = groups.get(key);
|
|
40
|
+
if (group) {
|
|
41
|
+
group.push(record);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
groups.set(key, [record]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Array.from(groups.values());
|
|
48
|
+
}
|
|
49
|
+
generalize(records, quasiIdentifiers, level) {
|
|
50
|
+
return records.map(record => {
|
|
51
|
+
const generalized = { ...record };
|
|
52
|
+
for (const qi of quasiIdentifiers) {
|
|
53
|
+
const value = String(generalized[qi] ?? '');
|
|
54
|
+
if (value.length > level + 1) {
|
|
55
|
+
generalized[qi] = value.substring(0, value.length - level - 1) + '*'.repeat(level + 1);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
generalized[qi] = '*'.repeat(value.length);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return generalized;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=kAnonymityAlgorithm.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class BudgetTracker {
|
|
2
|
+
consumed = 0;
|
|
3
|
+
queryCount = 0;
|
|
4
|
+
totalBudget;
|
|
5
|
+
constructor(totalBudget) {
|
|
6
|
+
this.totalBudget = totalBudget;
|
|
7
|
+
}
|
|
8
|
+
consume(epsilon) {
|
|
9
|
+
this.consumed += epsilon;
|
|
10
|
+
this.queryCount++;
|
|
11
|
+
}
|
|
12
|
+
canConsume(epsilon, totalBudget) {
|
|
13
|
+
return this.consumed + epsilon <= totalBudget;
|
|
14
|
+
}
|
|
15
|
+
getState() {
|
|
16
|
+
return {
|
|
17
|
+
totalBudget: this.totalBudget,
|
|
18
|
+
consumed: this.consumed,
|
|
19
|
+
remaining: this.totalBudget - this.consumed,
|
|
20
|
+
queryCount: this.queryCount,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=budgetTracker.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const defaultAdvancedMaskingConfig = {
|
|
2
|
+
defaultAlgorithm: 'hash',
|
|
3
|
+
fieldAlgorithms: {},
|
|
4
|
+
fpe: {
|
|
5
|
+
key: '',
|
|
6
|
+
radix: 10,
|
|
7
|
+
},
|
|
8
|
+
kAnonymity: {
|
|
9
|
+
kValue: 2,
|
|
10
|
+
quasiIdentifiers: ['zipCode', 'age', 'gender'],
|
|
11
|
+
},
|
|
12
|
+
differentialPrivacy: {
|
|
13
|
+
epsilon: 1.0,
|
|
14
|
+
totalBudget: 5.0,
|
|
15
|
+
sensitivity: 1.0,
|
|
16
|
+
},
|
|
17
|
+
hash: {
|
|
18
|
+
algorithm: 'SHA-256',
|
|
19
|
+
salt: '',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=defaultAdvancedMaskingConfig.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class MaskingStrategyExecutor {
|
|
2
|
+
execute(value, _type, strategy) {
|
|
3
|
+
const resolvedStrategy = this.resolveStrategy(strategy);
|
|
4
|
+
switch (resolvedStrategy) {
|
|
5
|
+
case 'FULL':
|
|
6
|
+
return '***';
|
|
7
|
+
case 'PARTIAL':
|
|
8
|
+
return this.applyPartial(value);
|
|
9
|
+
case 'GENERALIZE':
|
|
10
|
+
return '<脱敏数据>';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
resolveStrategy(strategy) {
|
|
14
|
+
if (strategy === 'FULL' || strategy === 'PARTIAL' || strategy === 'GENERALIZE') {
|
|
15
|
+
return strategy;
|
|
16
|
+
}
|
|
17
|
+
return 'PARTIAL';
|
|
18
|
+
}
|
|
19
|
+
applyPartial(value, keepPrefix = 3, keepSuffix = 4) {
|
|
20
|
+
if (value.length <= keepPrefix + keepSuffix) {
|
|
21
|
+
return '***';
|
|
22
|
+
}
|
|
23
|
+
return value.slice(0, keepPrefix) + '***' + value.slice(-keepSuffix);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=maskingStrategyExecutor.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export class SensitiveFieldScanner {
|
|
2
|
+
scan(lines, sensitivePatterns) {
|
|
3
|
+
const fields = [];
|
|
4
|
+
const typeCounts = {};
|
|
5
|
+
const patternEntries = Object.entries(sensitivePatterns);
|
|
6
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
7
|
+
const line = lines[lineIndex] ?? '';
|
|
8
|
+
for (const [type, config] of patternEntries) {
|
|
9
|
+
const regex = new RegExp(config.pattern, 'g');
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = regex.exec(line)) !== null) {
|
|
12
|
+
fields.push({
|
|
13
|
+
type,
|
|
14
|
+
value: match[0],
|
|
15
|
+
line: lineIndex + 1,
|
|
16
|
+
column: match.index + 1,
|
|
17
|
+
});
|
|
18
|
+
typeCounts[type] = (typeCounts[type] ?? 0) + 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { fields, typeCounts };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sensitiveFieldScanner.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liuhange/dsh-data-masking",
|
|
3
3
|
"description": "Data masking plugin: sensitive field identification and graded masking (FULL/PARTIAL/GENERALIZE)",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.3",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
"./package.json": "./package.json"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
-
"lib/
|
|
30
|
-
"lib/invariant.js",
|
|
31
|
-
"lib/types/**/*.d.ts"
|
|
29
|
+
"lib/**/*.js", "lib/types/**/*.d.ts"
|
|
32
30
|
],
|
|
33
31
|
"license": "MIT",
|
|
34
32
|
"scripts": {
|