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