no-pii 1.2.1 → 1.2.2
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/nopii.js +18 -108
- package/package.json +4 -1
package/nopii.js
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import crypto from 'node:crypto';
|
|
3
|
-
import { Buffer } from 'node:buffer';
|
|
4
|
-
import { getPassword, setPassword } from 'cross-keychain';
|
|
5
|
-
|
|
6
|
-
export class NoPii {
|
|
1
|
+
export default class NoPii {
|
|
7
2
|
#strategies = [];
|
|
8
3
|
#combinedRegex = null;
|
|
9
|
-
#storage = null;
|
|
10
4
|
#config = {};
|
|
11
|
-
#currentRules = {};
|
|
5
|
+
#currentRules = {};
|
|
12
6
|
|
|
13
7
|
static PRESETS = {
|
|
14
8
|
COMMON: {
|
|
@@ -24,7 +18,6 @@ export class NoPii {
|
|
|
24
18
|
|
|
25
19
|
constructor(options = {}) {
|
|
26
20
|
this.#config = { verbose: false, ...options };
|
|
27
|
-
this.#storage = this.#initStorage(options.storage);
|
|
28
21
|
|
|
29
22
|
if (options.rules) {
|
|
30
23
|
for (const [key, val] of Object.entries(options.rules)) {
|
|
@@ -33,13 +26,12 @@ export class NoPii {
|
|
|
33
26
|
}
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
29
|
+
listRules() {
|
|
37
30
|
return { ...this.#currentRules };
|
|
38
31
|
}
|
|
39
32
|
|
|
40
|
-
|
|
33
|
+
addRule(key, value) {
|
|
41
34
|
this.#applyRuleLogic(key, value);
|
|
42
|
-
await this.#persist();
|
|
43
35
|
return this;
|
|
44
36
|
}
|
|
45
37
|
|
|
@@ -57,13 +49,12 @@ export class NoPii {
|
|
|
57
49
|
else if (allPresets[upperKey] && value === true) {
|
|
58
50
|
this.#register(upperKey, allPresets[upperKey]);
|
|
59
51
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
52
|
+
else if (Array.isArray(value)) {
|
|
53
|
+
const escaped = value
|
|
54
|
+
.map(v => v.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
|
|
55
|
+
.join('|');
|
|
56
|
+
this.#register(key, new RegExp(escaped, 'g'));
|
|
57
|
+
}
|
|
67
58
|
else {
|
|
68
59
|
this.#register(key, value);
|
|
69
60
|
}
|
|
@@ -71,7 +62,6 @@ export class NoPii {
|
|
|
71
62
|
|
|
72
63
|
#register(name, regex) {
|
|
73
64
|
let pattern;
|
|
74
|
-
// FIX: Handle string-serialized regex like "/pattern/flags"
|
|
75
65
|
if (typeof regex === 'string' && regex.startsWith('/') && regex.lastIndexOf('/') > 0) {
|
|
76
66
|
const lastSlash = regex.lastIndexOf('/');
|
|
77
67
|
const source = regex.slice(1, lastSlash);
|
|
@@ -101,38 +91,16 @@ export class NoPii {
|
|
|
101
91
|
this.#combinedRegex = new RegExp(source, 'gu');
|
|
102
92
|
}
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
if (!this.#storage) return;
|
|
106
|
-
|
|
107
|
-
// FIX: Serialize RegExp objects to strings so they don't become {} in JSON
|
|
108
|
-
const serializable = {};
|
|
109
|
-
for (const [k, v] of Object.entries(this.#currentRules)) {
|
|
110
|
-
serializable[k] = (v instanceof RegExp) ? `/${v.source}/${v.flags}` : v;
|
|
111
|
-
}
|
|
112
|
-
await this.#storage.set('rules', serializable);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async load() {
|
|
116
|
-
if (!this.#storage) return this;
|
|
117
|
-
const savedRules = await this.#storage.get('rules');
|
|
118
|
-
if (savedRules) {
|
|
119
|
-
for (const [key, val] of Object.entries(savedRules)) {
|
|
120
|
-
this.#applyRuleLogic(key, val);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return this;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
redact(text) {
|
|
94
|
+
redact(text, startCount) {
|
|
127
95
|
if (!this.#combinedRegex || !text) return { safeText: text, vault: new Map() };
|
|
128
|
-
const vault =
|
|
129
|
-
let count = 0;
|
|
96
|
+
const vault = {};
|
|
97
|
+
let count = startCount ? startCount : 0;
|
|
130
98
|
const safeText = text.replace(this.#combinedRegex, (...args) => {
|
|
131
99
|
const groups = args.at(-1);
|
|
132
100
|
const label = Object.keys(groups).find(key => groups[key] !== undefined);
|
|
133
101
|
const value = groups[label];
|
|
134
102
|
const placeholder = `[${label}_${++count}]`;
|
|
135
|
-
vault
|
|
103
|
+
vault[placeholder] = value;
|
|
136
104
|
return placeholder;
|
|
137
105
|
});
|
|
138
106
|
return { safeText, vault };
|
|
@@ -152,68 +120,10 @@ export class NoPii {
|
|
|
152
120
|
|
|
153
121
|
return redactedText.replace(restoreRegex, (m) => lookup(m));
|
|
154
122
|
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
155
126
|
|
|
156
|
-
|
|
157
|
-
if (!storage) {
|
|
158
|
-
const _mem = new Map();
|
|
159
|
-
return { get: async (k) => _mem.get(k), set: async (k, v) => _mem.set(k, v) };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (storage.method === 'os') {
|
|
163
|
-
const { service = 'nopii' } = storage;
|
|
164
|
-
const account = service;
|
|
165
|
-
return {
|
|
166
|
-
get: async (key) => {
|
|
167
|
-
try {
|
|
168
|
-
const val = await getPassword(service, `${account}_${key}`);
|
|
169
|
-
return val ? JSON.parse(val) : null;
|
|
170
|
-
} catch { return null; }
|
|
171
|
-
},
|
|
172
|
-
set: async (key, val) => {
|
|
173
|
-
await setPassword(service, `${account}_${key}`, JSON.stringify(val));
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (typeof storage.method === 'string' && storage.method.endsWith('.json')) {
|
|
179
|
-
const aesKey = Buffer.alloc(32, storage.aes || 'default-secret-key');
|
|
180
|
-
const filePath = storage.method;
|
|
181
|
-
|
|
182
|
-
const encrypt = (text) => {
|
|
183
|
-
const iv = crypto.randomBytes(12);
|
|
184
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);
|
|
185
|
-
const enc = Buffer.concat([cipher.update(text, 'utf8'), cipher.final()]);
|
|
186
|
-
return Buffer.concat([iv, cipher.getAuthTag(), enc]).toString('base64');
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
const decrypt = (data) => {
|
|
190
|
-
const buf = Buffer.from(data, 'base64');
|
|
191
|
-
const iv = buf.subarray(0, 12), tag = buf.subarray(12, 28), enc = buf.subarray(28);
|
|
192
|
-
const decipher = crypto.createDecipheriv('aes-256-gcm', aesKey, iv);
|
|
193
|
-
decipher.setAuthTag(tag);
|
|
194
|
-
return decipher.update(enc, 'utf8') + decipher.final('utf8');
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
async get(lookupKey) {
|
|
199
|
-
try {
|
|
200
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
201
|
-
const data = JSON.parse(decrypt(raw));
|
|
202
|
-
return lookupKey ? data[lookupKey] : data;
|
|
203
|
-
} catch { return null; }
|
|
204
|
-
},
|
|
205
|
-
async set(lookupKey, val) {
|
|
206
|
-
let data = {};
|
|
207
|
-
try {
|
|
208
|
-
const raw = await fs.readFile(filePath, 'utf8');
|
|
209
|
-
data = JSON.parse(decrypt(raw));
|
|
210
|
-
} catch (e) {}
|
|
211
|
-
data[lookupKey] = val;
|
|
212
|
-
await fs.writeFile(filePath, encrypt(JSON.stringify(data)));
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
127
|
+
|
|
217
128
|
}
|
|
218
129
|
|
|
219
|
-
export const nopii = (options) => new NoPii(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "no-pii",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Production-grade PII redaction library with CLI support",
|
|
5
5
|
"main": "nopii.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@huggingface/transformers": "^3.8.1",
|
|
34
|
+
"@nlpjs/lang-zh": "^5.0.0-alpha.5",
|
|
35
|
+
"@nlpjs/ner": "^5.0.0-alpha.5",
|
|
34
36
|
"cross-keychain": "^1.0.0",
|
|
37
|
+
"nodejieba": "^3.5.7",
|
|
35
38
|
"onnxruntime-node": "^1.24.3"
|
|
36
39
|
},
|
|
37
40
|
"files": [
|