@elanlanguages/bridge-anonymization 0.1.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.
- package/README.md +382 -0
- package/dist/crypto/index.d.ts +6 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +6 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/crypto/pii-map-crypto.d.ts +100 -0
- package/dist/crypto/pii-map-crypto.d.ts.map +1 -0
- package/dist/crypto/pii-map-crypto.js +163 -0
- package/dist/crypto/pii-map-crypto.js.map +1 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -0
- package/dist/ner/bio-decoder.d.ts +64 -0
- package/dist/ner/bio-decoder.d.ts.map +1 -0
- package/dist/ner/bio-decoder.js +216 -0
- package/dist/ner/bio-decoder.js.map +1 -0
- package/dist/ner/index.d.ts +10 -0
- package/dist/ner/index.d.ts.map +1 -0
- package/dist/ner/index.js +10 -0
- package/dist/ner/index.js.map +1 -0
- package/dist/ner/model-manager.d.ts +102 -0
- package/dist/ner/model-manager.d.ts.map +1 -0
- package/dist/ner/model-manager.js +253 -0
- package/dist/ner/model-manager.js.map +1 -0
- package/dist/ner/ner-model.d.ts +114 -0
- package/dist/ner/ner-model.d.ts.map +1 -0
- package/dist/ner/ner-model.js +240 -0
- package/dist/ner/ner-model.js.map +1 -0
- package/dist/ner/onnx-runtime.d.ts +45 -0
- package/dist/ner/onnx-runtime.d.ts.map +1 -0
- package/dist/ner/onnx-runtime.js +99 -0
- package/dist/ner/onnx-runtime.js.map +1 -0
- package/dist/ner/tokenizer.d.ts +140 -0
- package/dist/ner/tokenizer.d.ts.map +1 -0
- package/dist/ner/tokenizer.js +341 -0
- package/dist/ner/tokenizer.js.map +1 -0
- package/dist/pipeline/index.d.ts +9 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +9 -0
- package/dist/pipeline/index.js.map +1 -0
- package/dist/pipeline/prenormalize.d.ts +48 -0
- package/dist/pipeline/prenormalize.d.ts.map +1 -0
- package/dist/pipeline/prenormalize.js +94 -0
- package/dist/pipeline/prenormalize.js.map +1 -0
- package/dist/pipeline/resolver.d.ts +56 -0
- package/dist/pipeline/resolver.d.ts.map +1 -0
- package/dist/pipeline/resolver.js +238 -0
- package/dist/pipeline/resolver.js.map +1 -0
- package/dist/pipeline/tagger.d.ts +74 -0
- package/dist/pipeline/tagger.d.ts.map +1 -0
- package/dist/pipeline/tagger.js +169 -0
- package/dist/pipeline/tagger.js.map +1 -0
- package/dist/pipeline/validator.d.ts +65 -0
- package/dist/pipeline/validator.d.ts.map +1 -0
- package/dist/pipeline/validator.js +264 -0
- package/dist/pipeline/validator.js.map +1 -0
- package/dist/recognizers/base.d.ts +78 -0
- package/dist/recognizers/base.d.ts.map +1 -0
- package/dist/recognizers/base.js +100 -0
- package/dist/recognizers/base.js.map +1 -0
- package/dist/recognizers/bic-swift.d.ts +10 -0
- package/dist/recognizers/bic-swift.d.ts.map +1 -0
- package/dist/recognizers/bic-swift.js +107 -0
- package/dist/recognizers/bic-swift.js.map +1 -0
- package/dist/recognizers/credit-card.d.ts +32 -0
- package/dist/recognizers/credit-card.d.ts.map +1 -0
- package/dist/recognizers/credit-card.js +160 -0
- package/dist/recognizers/credit-card.js.map +1 -0
- package/dist/recognizers/custom-id.d.ts +28 -0
- package/dist/recognizers/custom-id.d.ts.map +1 -0
- package/dist/recognizers/custom-id.js +116 -0
- package/dist/recognizers/custom-id.js.map +1 -0
- package/dist/recognizers/email.d.ts +10 -0
- package/dist/recognizers/email.d.ts.map +1 -0
- package/dist/recognizers/email.js +75 -0
- package/dist/recognizers/email.js.map +1 -0
- package/dist/recognizers/iban.d.ts +14 -0
- package/dist/recognizers/iban.d.ts.map +1 -0
- package/dist/recognizers/iban.js +67 -0
- package/dist/recognizers/iban.js.map +1 -0
- package/dist/recognizers/index.d.ts +20 -0
- package/dist/recognizers/index.d.ts.map +1 -0
- package/dist/recognizers/index.js +42 -0
- package/dist/recognizers/index.js.map +1 -0
- package/dist/recognizers/ip-address.d.ts +14 -0
- package/dist/recognizers/ip-address.d.ts.map +1 -0
- package/dist/recognizers/ip-address.js +183 -0
- package/dist/recognizers/ip-address.js.map +1 -0
- package/dist/recognizers/phone.d.ts +10 -0
- package/dist/recognizers/phone.d.ts.map +1 -0
- package/dist/recognizers/phone.js +145 -0
- package/dist/recognizers/phone.js.map +1 -0
- package/dist/recognizers/registry.d.ts +59 -0
- package/dist/recognizers/registry.d.ts.map +1 -0
- package/dist/recognizers/registry.js +113 -0
- package/dist/recognizers/registry.js.map +1 -0
- package/dist/recognizers/url.d.ts +14 -0
- package/dist/recognizers/url.d.ts.map +1 -0
- package/dist/recognizers/url.js +121 -0
- package/dist/recognizers/url.js.map +1 -0
- package/dist/types/index.d.ts +134 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +69 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pii-types.d.ts +50 -0
- package/dist/types/pii-types.d.ts.map +1 -0
- package/dist/types/pii-types.js +114 -0
- package/dist/types/pii-types.js.map +1 -0
- package/dist/utils/iban-checksum.d.ts +23 -0
- package/dist/utils/iban-checksum.d.ts.map +1 -0
- package/dist/utils/iban-checksum.js +106 -0
- package/dist/utils/iban-checksum.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/luhn.d.ts +17 -0
- package/dist/utils/luhn.d.ts.map +1 -0
- package/dist/utils/luhn.js +55 -0
- package/dist/utils/luhn.js.map +1 -0
- package/dist/utils/offsets.d.ts +86 -0
- package/dist/utils/offsets.d.ts.map +1 -0
- package/dist/utils/offsets.js +124 -0
- package/dist/utils/offsets.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# Bridge Anonymization
|
|
2
|
+
|
|
3
|
+
On-device PII anonymization module for high-privacy translation workflows. Detects and replaces Personally Identifiable Information (PII) with placeholder tags while maintaining an encrypted mapping for later rehydration.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Structured PII Detection**: Regex-based detection for emails, phones, IBANs, credit cards, IPs, URLs
|
|
8
|
+
- **Soft PII Detection**: ONNX-powered NER model for names, organizations, locations (auto-downloads on first use)
|
|
9
|
+
- **Secure PII Mapping**: AES-256-GCM encrypted storage of original PII values
|
|
10
|
+
- **Configurable Policies**: Customizable detection rules, thresholds, and allowlists
|
|
11
|
+
- **Validation & Leak Scanning**: Built-in validation and optional leak detection
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install bridge-anonymization
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> **Bun users**: Install `onnxruntime-web` additionally: `bun add bridge-anonymization onnxruntime-web`
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Regex-Only Mode (No Downloads Required)
|
|
24
|
+
|
|
25
|
+
For structured PII like emails, phones, IBANs, credit cards:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { anonymizeRegexOnly } from 'bridge-anonymization';
|
|
29
|
+
|
|
30
|
+
const result = await anonymizeRegexOnly(
|
|
31
|
+
'Contact john@example.com or call +49 30 123456. IBAN: DE89370400440532013000'
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
console.log(result.anonymizedText);
|
|
35
|
+
// "Contact <PII type="EMAIL" id="1"/> or call <PII type="PHONE" id="2"/>. IBAN: <PII type="IBAN" id="3"/>"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Full Mode with NER (Detects Names, Organizations, Locations)
|
|
39
|
+
|
|
40
|
+
The NER model is automatically downloaded on first use (~280 MB for quantized):
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { createAnonymizer } from 'bridge-anonymization';
|
|
44
|
+
|
|
45
|
+
const anonymizer = createAnonymizer({
|
|
46
|
+
ner: {
|
|
47
|
+
mode: 'quantized', // or 'standard' for full model (~1.1 GB)
|
|
48
|
+
onStatus: (status) => console.log(status), // Optional progress
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await anonymizer.initialize(); // Downloads model if needed
|
|
53
|
+
|
|
54
|
+
const result = await anonymizer.anonymize(
|
|
55
|
+
'Hello John Smith from Acme Corp in Berlin!'
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log(result.anonymizedText);
|
|
59
|
+
// "Hello <PII type="PERSON" id="1"/> from <PII type="ORG" id="2"/> in <PII type="LOCATION" id="3"/>!"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### One-liner with NER
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { anonymizeWithNER } from 'bridge-anonymization';
|
|
66
|
+
|
|
67
|
+
const result = await anonymizeWithNER(
|
|
68
|
+
'Contact John Smith at john@example.com',
|
|
69
|
+
{ mode: 'quantized', onStatus: console.log }
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
### Configuration Options
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { createAnonymizer, InMemoryKeyProvider } from 'bridge-anonymization';
|
|
79
|
+
|
|
80
|
+
const anonymizer = createAnonymizer({
|
|
81
|
+
// NER configuration
|
|
82
|
+
ner: {
|
|
83
|
+
mode: 'quantized', // 'standard' | 'quantized' | 'disabled' | 'custom'
|
|
84
|
+
autoDownload: true, // Auto-download model if not present
|
|
85
|
+
onStatus: (s) => {}, // Status messages callback
|
|
86
|
+
onDownloadProgress: (p) => {}, // Download progress callback
|
|
87
|
+
|
|
88
|
+
// For 'custom' mode only:
|
|
89
|
+
modelPath: './my-model.onnx',
|
|
90
|
+
vocabPath: './vocab.txt',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Encryption key provider
|
|
94
|
+
keyProvider: new InMemoryKeyProvider(),
|
|
95
|
+
|
|
96
|
+
// Custom policy
|
|
97
|
+
defaultPolicy: { /* ... */ },
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
await anonymizer.initialize();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### NER Modes
|
|
104
|
+
|
|
105
|
+
| Mode | Description | Size | Auto-Download |
|
|
106
|
+
|------|-------------|------|---------------|
|
|
107
|
+
| `'disabled'` | No NER, regex only | 0 | N/A |
|
|
108
|
+
| `'quantized'` | Smaller model, ~95% accuracy | ~280 MB | Yes |
|
|
109
|
+
| `'standard'` | Full model, best accuracy | ~1.1 GB | Yes |
|
|
110
|
+
| `'custom'` | Your own ONNX model | Varies | No |
|
|
111
|
+
|
|
112
|
+
### Main Functions
|
|
113
|
+
|
|
114
|
+
#### `createAnonymizer(config?)`
|
|
115
|
+
|
|
116
|
+
Creates an anonymizer instance:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const anonymizer = createAnonymizer({
|
|
120
|
+
ner: { mode: 'quantized' }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
await anonymizer.initialize();
|
|
124
|
+
const result = await anonymizer.anonymize('text');
|
|
125
|
+
await anonymizer.dispose();
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `anonymize(text, locale?, policy?)`
|
|
129
|
+
|
|
130
|
+
One-off anonymization (regex-only by default):
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const result = await anonymize('Contact test@example.com');
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `anonymizeWithNER(text, nerConfig, policy?)`
|
|
137
|
+
|
|
138
|
+
One-off anonymization with NER:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
const result = await anonymizeWithNER(
|
|
142
|
+
'Hello John Smith',
|
|
143
|
+
{ mode: 'quantized' }
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### `anonymizeRegexOnly(text, policy?)`
|
|
148
|
+
|
|
149
|
+
Fast regex-only anonymization:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
const result = await anonymizeRegexOnly('Card: 4111111111111111');
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Result Structure
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
interface AnonymizationResult {
|
|
159
|
+
// Text with PII replaced by placeholder tags
|
|
160
|
+
anonymizedText: string;
|
|
161
|
+
|
|
162
|
+
// Detected entities (without original text for safety)
|
|
163
|
+
entities: Array<{
|
|
164
|
+
type: PIIType;
|
|
165
|
+
id: number;
|
|
166
|
+
start: number;
|
|
167
|
+
end: number;
|
|
168
|
+
confidence: number;
|
|
169
|
+
source: 'REGEX' | 'NER';
|
|
170
|
+
}>;
|
|
171
|
+
|
|
172
|
+
// Encrypted PII mapping (for later rehydration)
|
|
173
|
+
piiMap: {
|
|
174
|
+
ciphertext: string; // Base64
|
|
175
|
+
iv: string; // Base64
|
|
176
|
+
authTag: string; // Base64
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Processing statistics
|
|
180
|
+
stats: {
|
|
181
|
+
countsByType: Record<PIIType, number>;
|
|
182
|
+
totalEntities: number;
|
|
183
|
+
processingTimeMs: number;
|
|
184
|
+
modelVersion: string;
|
|
185
|
+
leakScanPassed?: boolean;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Supported PII Types
|
|
191
|
+
|
|
192
|
+
| Type | Description | Detection Method |
|
|
193
|
+
|------|-------------|------------------|
|
|
194
|
+
| `EMAIL` | Email addresses | Regex |
|
|
195
|
+
| `PHONE` | Phone numbers (international) | Regex |
|
|
196
|
+
| `IBAN` | International Bank Account Numbers | Regex + Checksum |
|
|
197
|
+
| `BIC_SWIFT` | Bank Identifier Codes | Regex |
|
|
198
|
+
| `CREDIT_CARD` | Credit card numbers | Regex + Luhn |
|
|
199
|
+
| `IP_ADDRESS` | IPv4 and IPv6 addresses | Regex |
|
|
200
|
+
| `URL` | Web URLs | Regex |
|
|
201
|
+
| `CASE_ID` | Case/ticket numbers | Regex (configurable) |
|
|
202
|
+
| `CUSTOMER_ID` | Customer identifiers | Regex (configurable) |
|
|
203
|
+
| `PERSON` | Person names | NER |
|
|
204
|
+
| `ORG` | Organization names | NER |
|
|
205
|
+
| `LOCATION` | Location/place names | NER |
|
|
206
|
+
| `ADDRESS` | Physical addresses | NER |
|
|
207
|
+
| `DATE_OF_BIRTH` | Dates of birth | NER |
|
|
208
|
+
|
|
209
|
+
## Configuration
|
|
210
|
+
|
|
211
|
+
### Anonymization Policy
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { createAnonymizer, PIIType } from 'bridge-anonymization';
|
|
215
|
+
|
|
216
|
+
const anonymizer = createAnonymizer({
|
|
217
|
+
ner: { mode: 'quantized' },
|
|
218
|
+
defaultPolicy: {
|
|
219
|
+
// Which PII types to detect
|
|
220
|
+
enabledTypes: new Set([PIIType.EMAIL, PIIType.PHONE, PIIType.PERSON]),
|
|
221
|
+
|
|
222
|
+
// Confidence thresholds per type (0.0 - 1.0)
|
|
223
|
+
confidenceThresholds: new Map([
|
|
224
|
+
[PIIType.PERSON, 0.8],
|
|
225
|
+
[PIIType.EMAIL, 0.5],
|
|
226
|
+
]),
|
|
227
|
+
|
|
228
|
+
// Terms to never treat as PII
|
|
229
|
+
allowlistTerms: new Set(['Customer Service', 'Help Desk']),
|
|
230
|
+
|
|
231
|
+
// Enable leak scanning on output
|
|
232
|
+
enableLeakScan: true,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Custom Recognizers
|
|
238
|
+
|
|
239
|
+
Add domain-specific patterns:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { createCustomIdRecognizer, PIIType, createAnonymizer } from 'bridge-anonymization';
|
|
243
|
+
|
|
244
|
+
const customRecognizer = createCustomIdRecognizer([
|
|
245
|
+
{
|
|
246
|
+
name: 'Order Number',
|
|
247
|
+
pattern: /\bORD-[A-Z0-9]{8}\b/g,
|
|
248
|
+
type: PIIType.CASE_ID,
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
const anonymizer = createAnonymizer();
|
|
253
|
+
anonymizer.getRegistry().register(customRecognizer);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Model Management
|
|
257
|
+
|
|
258
|
+
Models are hosted on [Hugging Face Hub](https://huggingface.co/Xenova/xlm-roberta-base-ner-hrl) and automatically downloaded on first use.
|
|
259
|
+
|
|
260
|
+
**Cache locations:**
|
|
261
|
+
- **macOS**: `~/Library/Caches/bridge-anonymization/models/`
|
|
262
|
+
- **Linux**: `~/.cache/bridge-anonymization/models/`
|
|
263
|
+
- **Windows**: `%LOCALAPPDATA%/bridge-anonymization/models/`
|
|
264
|
+
|
|
265
|
+
### Manual Model Management
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import {
|
|
269
|
+
isModelDownloaded,
|
|
270
|
+
downloadModel,
|
|
271
|
+
clearModelCache,
|
|
272
|
+
listDownloadedModels,
|
|
273
|
+
getModelCacheDir
|
|
274
|
+
} from 'bridge-anonymization';
|
|
275
|
+
|
|
276
|
+
// Check if model is downloaded
|
|
277
|
+
const hasModel = await isModelDownloaded('quantized');
|
|
278
|
+
|
|
279
|
+
// Manually download
|
|
280
|
+
await downloadModel('quantized', (progress) => {
|
|
281
|
+
console.log(`${progress.file}: ${progress.percent}%`);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// List downloaded models
|
|
285
|
+
const models = await listDownloadedModels();
|
|
286
|
+
|
|
287
|
+
// Clear cache
|
|
288
|
+
await clearModelCache('quantized'); // or clearModelCache() for all
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Encryption & Security
|
|
292
|
+
|
|
293
|
+
The PII map is encrypted using AES-256-GCM:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { createAnonymizer, KeyProvider, generateKey } from 'bridge-anonymization';
|
|
297
|
+
|
|
298
|
+
class SecureKeyProvider implements KeyProvider {
|
|
299
|
+
async getKey(): Promise<Buffer> {
|
|
300
|
+
// Retrieve from OS keychain, HSM, or secure storage
|
|
301
|
+
return await getKeyFromSecureStorage();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const anonymizer = createAnonymizer({
|
|
306
|
+
keyProvider: new SecureKeyProvider(),
|
|
307
|
+
ner: { mode: 'quantized' },
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Security Best Practices
|
|
312
|
+
|
|
313
|
+
- **Never log the raw PII map** - Always use encrypted storage
|
|
314
|
+
- **Rotate keys** - Implement key rotation for long-running applications
|
|
315
|
+
- **Use platform keystores** - iOS Keychain, Android Keystore, or OS credential managers
|
|
316
|
+
- **Enable leak scanning** - Catch any missed PII in output
|
|
317
|
+
|
|
318
|
+
## Bun Support
|
|
319
|
+
|
|
320
|
+
This library works with [Bun](https://bun.sh). Since `onnxruntime-node` is a native Node.js addon, Bun users need `onnxruntime-web`:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
bun add bridge-anonymization onnxruntime-web
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Usage is identical - the library auto-detects the runtime:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { createAnonymizer } from 'bridge-anonymization';
|
|
330
|
+
|
|
331
|
+
const anonymizer = createAnonymizer({
|
|
332
|
+
ner: { mode: 'quantized' }
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
await anonymizer.initialize();
|
|
336
|
+
const result = await anonymizer.anonymize('Hello John Smith');
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Performance
|
|
340
|
+
|
|
341
|
+
| Component | Time (2K chars) | Notes |
|
|
342
|
+
|-----------|-----------------|-------|
|
|
343
|
+
| Regex pass | ~5 ms | All regex recognizers |
|
|
344
|
+
| NER inference | ~100-150 ms | Quantized model |
|
|
345
|
+
| Total pipeline | ~150-200 ms | Full anonymization |
|
|
346
|
+
|
|
347
|
+
| Model | Size | First-Use Download |
|
|
348
|
+
|-------|------|-------------------|
|
|
349
|
+
| Quantized | ~280 MB | ~30s on fast connection |
|
|
350
|
+
| Standard | ~1.1 GB | ~2min on fast connection |
|
|
351
|
+
|
|
352
|
+
## Development
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# Install dependencies
|
|
356
|
+
npm install
|
|
357
|
+
|
|
358
|
+
# Run tests
|
|
359
|
+
npm test
|
|
360
|
+
|
|
361
|
+
# Build
|
|
362
|
+
npm run build
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Building Custom Models
|
|
366
|
+
|
|
367
|
+
For development or custom models, you can use the setup script:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Requires Python 3.8+
|
|
371
|
+
npm run setup:ner # Standard model
|
|
372
|
+
npm run setup:ner:quantized # Quantized model
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Requirements
|
|
376
|
+
|
|
377
|
+
- Node.js >= 18.0.0 (ONNX runtime included automatically)
|
|
378
|
+
- Bun >= 1.0.0 (requires `onnxruntime-web`)
|
|
379
|
+
|
|
380
|
+
## License
|
|
381
|
+
|
|
382
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII Map Encryption
|
|
3
|
+
* AES-256-GCM encryption for the PII mapping
|
|
4
|
+
*/
|
|
5
|
+
import { EncryptedPIIMap } from '../types/index.js';
|
|
6
|
+
import type { RawPIIMap } from '../pipeline/tagger.js';
|
|
7
|
+
/**
|
|
8
|
+
* Encryption configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface EncryptionConfig {
|
|
11
|
+
/** Algorithm (default: aes-256-gcm) */
|
|
12
|
+
algorithm: string;
|
|
13
|
+
/** IV length in bytes (default: 12 for GCM) */
|
|
14
|
+
ivLength: number;
|
|
15
|
+
/** Auth tag length in bytes (default: 16) */
|
|
16
|
+
authTagLength: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Default encryption configuration
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_ENCRYPTION_CONFIG: EncryptionConfig;
|
|
22
|
+
/**
|
|
23
|
+
* Key generation options
|
|
24
|
+
*/
|
|
25
|
+
export interface KeyGenOptions {
|
|
26
|
+
/** Key length in bytes (default: 32 for AES-256) */
|
|
27
|
+
length: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generates a random encryption key
|
|
31
|
+
* @returns Buffer containing the key
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateKey(options?: Partial<KeyGenOptions>): Buffer;
|
|
34
|
+
/**
|
|
35
|
+
* Derives a key from a password using PBKDF2
|
|
36
|
+
* @param password - Password string
|
|
37
|
+
* @param salt - Salt buffer (should be randomly generated and stored)
|
|
38
|
+
* @param iterations - Number of iterations (default: 100000)
|
|
39
|
+
* @returns Buffer containing the derived key
|
|
40
|
+
*/
|
|
41
|
+
export declare function deriveKey(password: string, salt: Buffer, iterations?: number): Buffer;
|
|
42
|
+
/**
|
|
43
|
+
* Generates a random salt for key derivation
|
|
44
|
+
* @param length - Salt length in bytes (default: 16)
|
|
45
|
+
* @returns Buffer containing the salt
|
|
46
|
+
*/
|
|
47
|
+
export declare function generateSalt(length?: number): Buffer;
|
|
48
|
+
/**
|
|
49
|
+
* Encrypts a PII map using AES-256-GCM
|
|
50
|
+
* @param piiMap - Raw PII map to encrypt
|
|
51
|
+
* @param key - 32-byte encryption key
|
|
52
|
+
* @param config - Encryption configuration
|
|
53
|
+
* @returns Encrypted PII map
|
|
54
|
+
*/
|
|
55
|
+
export declare function encryptPIIMap(piiMap: RawPIIMap, key: Buffer, config?: Partial<EncryptionConfig>): EncryptedPIIMap;
|
|
56
|
+
/**
|
|
57
|
+
* Decrypts an encrypted PII map
|
|
58
|
+
* @param encrypted - Encrypted PII map
|
|
59
|
+
* @param key - 32-byte encryption key
|
|
60
|
+
* @param config - Encryption configuration
|
|
61
|
+
* @returns Decrypted PII map
|
|
62
|
+
*/
|
|
63
|
+
export declare function decryptPIIMap(encrypted: EncryptedPIIMap, key: Buffer, config?: Partial<EncryptionConfig>): RawPIIMap;
|
|
64
|
+
/**
|
|
65
|
+
* Key provider interface for external key management
|
|
66
|
+
*/
|
|
67
|
+
export interface KeyProvider {
|
|
68
|
+
/** Gets the current encryption key */
|
|
69
|
+
getKey(): Promise<Buffer>;
|
|
70
|
+
/** Rotates to a new key (optional) */
|
|
71
|
+
rotateKey?(): Promise<Buffer>;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Simple in-memory key provider (for testing/development)
|
|
75
|
+
* WARNING: Not secure for production use
|
|
76
|
+
*/
|
|
77
|
+
export declare class InMemoryKeyProvider implements KeyProvider {
|
|
78
|
+
private key;
|
|
79
|
+
constructor(key?: Buffer);
|
|
80
|
+
getKey(): Promise<Buffer>;
|
|
81
|
+
rotateKey(): Promise<Buffer>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Environment variable key provider
|
|
85
|
+
* Reads key from environment variable (base64 encoded)
|
|
86
|
+
*/
|
|
87
|
+
export declare class EnvKeyProvider implements KeyProvider {
|
|
88
|
+
private envVarName;
|
|
89
|
+
constructor(envVarName?: string);
|
|
90
|
+
getKey(): Promise<Buffer>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Validates that a key is suitable for AES-256
|
|
94
|
+
*/
|
|
95
|
+
export declare function validateKey(key: Buffer): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* Securely compares two buffers (timing-safe)
|
|
98
|
+
*/
|
|
99
|
+
export declare function secureCompare(a: Buffer, b: Buffer): boolean;
|
|
100
|
+
//# sourceMappingURL=pii-map-crypto.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii-map-crypto.d.ts","sourceRoot":"","sources":["../../src/crypto/pii-map-crypto.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,gBAIvC,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,MAAM,CAGxE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,GAAE,MAAe,GAC1B,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM,GACrC,eAAe,CAwCjB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,eAAe,EAC1B,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,OAAO,CAAC,gBAAgB,CAAM,GACrC,SAAS,CAuCX;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1B,sCAAsC;IACtC,SAAS,CAAC,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,GAAG,CAAS;gBAER,GAAG,CAAC,EAAE,MAAM;IAIlB,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAIzB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;CAInC;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,WAAW;IAChD,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAA6B;IAI/C,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;CAahC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEhD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK3D"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII Map Encryption
|
|
3
|
+
* AES-256-GCM encryption for the PII mapping
|
|
4
|
+
*/
|
|
5
|
+
import * as crypto from 'crypto';
|
|
6
|
+
/**
|
|
7
|
+
* Default encryption configuration
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_ENCRYPTION_CONFIG = {
|
|
10
|
+
algorithm: 'aes-256-gcm',
|
|
11
|
+
ivLength: 12,
|
|
12
|
+
authTagLength: 16,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Generates a random encryption key
|
|
16
|
+
* @returns Buffer containing the key
|
|
17
|
+
*/
|
|
18
|
+
export function generateKey(options = {}) {
|
|
19
|
+
const length = options.length ?? 32;
|
|
20
|
+
return crypto.randomBytes(length);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Derives a key from a password using PBKDF2
|
|
24
|
+
* @param password - Password string
|
|
25
|
+
* @param salt - Salt buffer (should be randomly generated and stored)
|
|
26
|
+
* @param iterations - Number of iterations (default: 100000)
|
|
27
|
+
* @returns Buffer containing the derived key
|
|
28
|
+
*/
|
|
29
|
+
export function deriveKey(password, salt, iterations = 100000) {
|
|
30
|
+
return crypto.pbkdf2Sync(password, salt, iterations, 32, 'sha256');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generates a random salt for key derivation
|
|
34
|
+
* @param length - Salt length in bytes (default: 16)
|
|
35
|
+
* @returns Buffer containing the salt
|
|
36
|
+
*/
|
|
37
|
+
export function generateSalt(length = 16) {
|
|
38
|
+
return crypto.randomBytes(length);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Encrypts a PII map using AES-256-GCM
|
|
42
|
+
* @param piiMap - Raw PII map to encrypt
|
|
43
|
+
* @param key - 32-byte encryption key
|
|
44
|
+
* @param config - Encryption configuration
|
|
45
|
+
* @returns Encrypted PII map
|
|
46
|
+
*/
|
|
47
|
+
export function encryptPIIMap(piiMap, key, config = {}) {
|
|
48
|
+
const encConfig = { ...DEFAULT_ENCRYPTION_CONFIG, ...config };
|
|
49
|
+
// Validate key length
|
|
50
|
+
if (key.length !== 32) {
|
|
51
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
52
|
+
}
|
|
53
|
+
// Convert map to JSON
|
|
54
|
+
const mapObject = {};
|
|
55
|
+
for (const [k, v] of piiMap) {
|
|
56
|
+
mapObject[k] = v;
|
|
57
|
+
}
|
|
58
|
+
const plaintext = JSON.stringify(mapObject);
|
|
59
|
+
// Generate random IV
|
|
60
|
+
const iv = crypto.randomBytes(encConfig.ivLength);
|
|
61
|
+
// Create cipher
|
|
62
|
+
const cipher = crypto.createCipheriv(encConfig.algorithm, key, iv, { authTagLength: encConfig.authTagLength });
|
|
63
|
+
// Encrypt
|
|
64
|
+
const encrypted = Buffer.concat([
|
|
65
|
+
cipher.update(plaintext, 'utf8'),
|
|
66
|
+
cipher.final(),
|
|
67
|
+
]);
|
|
68
|
+
// Get auth tag
|
|
69
|
+
const authTag = cipher.getAuthTag();
|
|
70
|
+
return {
|
|
71
|
+
ciphertext: encrypted.toString('base64'),
|
|
72
|
+
iv: iv.toString('base64'),
|
|
73
|
+
authTag: authTag.toString('base64'),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Decrypts an encrypted PII map
|
|
78
|
+
* @param encrypted - Encrypted PII map
|
|
79
|
+
* @param key - 32-byte encryption key
|
|
80
|
+
* @param config - Encryption configuration
|
|
81
|
+
* @returns Decrypted PII map
|
|
82
|
+
*/
|
|
83
|
+
export function decryptPIIMap(encrypted, key, config = {}) {
|
|
84
|
+
const encConfig = { ...DEFAULT_ENCRYPTION_CONFIG, ...config };
|
|
85
|
+
// Validate key length
|
|
86
|
+
if (key.length !== 32) {
|
|
87
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
88
|
+
}
|
|
89
|
+
// Decode base64
|
|
90
|
+
const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');
|
|
91
|
+
const iv = Buffer.from(encrypted.iv, 'base64');
|
|
92
|
+
const authTag = Buffer.from(encrypted.authTag, 'base64');
|
|
93
|
+
// Create decipher
|
|
94
|
+
const decipher = crypto.createDecipheriv(encConfig.algorithm, key, iv, { authTagLength: encConfig.authTagLength });
|
|
95
|
+
// Set auth tag
|
|
96
|
+
decipher.setAuthTag(authTag);
|
|
97
|
+
// Decrypt
|
|
98
|
+
const decrypted = Buffer.concat([
|
|
99
|
+
decipher.update(ciphertext),
|
|
100
|
+
decipher.final(),
|
|
101
|
+
]);
|
|
102
|
+
// Parse JSON back to map
|
|
103
|
+
const mapObject = JSON.parse(decrypted.toString('utf8'));
|
|
104
|
+
const piiMap = new Map();
|
|
105
|
+
for (const [k, v] of Object.entries(mapObject)) {
|
|
106
|
+
piiMap.set(k, v);
|
|
107
|
+
}
|
|
108
|
+
return piiMap;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Simple in-memory key provider (for testing/development)
|
|
112
|
+
* WARNING: Not secure for production use
|
|
113
|
+
*/
|
|
114
|
+
export class InMemoryKeyProvider {
|
|
115
|
+
key;
|
|
116
|
+
constructor(key) {
|
|
117
|
+
this.key = key ?? generateKey();
|
|
118
|
+
}
|
|
119
|
+
async getKey() {
|
|
120
|
+
return this.key;
|
|
121
|
+
}
|
|
122
|
+
async rotateKey() {
|
|
123
|
+
this.key = generateKey();
|
|
124
|
+
return this.key;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Environment variable key provider
|
|
129
|
+
* Reads key from environment variable (base64 encoded)
|
|
130
|
+
*/
|
|
131
|
+
export class EnvKeyProvider {
|
|
132
|
+
envVarName;
|
|
133
|
+
constructor(envVarName = 'PII_ENCRYPTION_KEY') {
|
|
134
|
+
this.envVarName = envVarName;
|
|
135
|
+
}
|
|
136
|
+
async getKey() {
|
|
137
|
+
const keyBase64 = process.env[this.envVarName];
|
|
138
|
+
if (keyBase64 === undefined || keyBase64.length === 0) {
|
|
139
|
+
throw new Error(`Encryption key not found in environment variable: ${this.envVarName}`);
|
|
140
|
+
}
|
|
141
|
+
const key = Buffer.from(keyBase64, 'base64');
|
|
142
|
+
if (key.length !== 32) {
|
|
143
|
+
throw new Error(`Invalid key length from ${this.envVarName}: expected 32 bytes`);
|
|
144
|
+
}
|
|
145
|
+
return key;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Validates that a key is suitable for AES-256
|
|
150
|
+
*/
|
|
151
|
+
export function validateKey(key) {
|
|
152
|
+
return key.length === 32;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Securely compares two buffers (timing-safe)
|
|
156
|
+
*/
|
|
157
|
+
export function secureCompare(a, b) {
|
|
158
|
+
if (a.length !== b.length) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return crypto.timingSafeEqual(a, b);
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=pii-map-crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pii-map-crypto.js","sourceRoot":"","sources":["../../src/crypto/pii-map-crypto.ts"],"names":[],"mappings":"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"}
|