@link-foundation/sensitive-data-sanitizer 0.17.1 → 0.19.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/docs/COVERAGE.md CHANGED
@@ -60,7 +60,7 @@ The `documents` profile also reads employers from CV experience lines anchored o
60
60
  a date range: `Title, Employer (2019-05 – 2021-03)`, `Title — Employer, 2019–2021`
61
61
  and `Title (Employer), 2019–2021`. Ranges use years, `YYYY-MM`, `MM.YYYY`, full
62
62
  dates or `month YYYY`, with `по настоящее время`, `н.в.`, `по н.в.`, `present` and
63
- similar open ends. Titles have one to nine words. A label of one or two words
63
+ similar open ends. For the non-parenthesized layouts, titles have one to nine words. A label of one or two words
64
64
  before the title (`Опыт: `, `Опыт работы - `, `Опыт — `) is read like a list marker,
65
65
  and a segment after `|` is read like a line start, so
66
66
  `Должность: Менеджер | Опыт: Аналитик, Ромашка (2018 – 2020)` also yields the
@@ -68,9 +68,9 @@ employer. The date range stays the anchor and the employer must still start with
68
68
  capital letter, a digit or a quote. Confidence is 0.60, and the employer then
69
69
  propagates through the document like other organizations. The default profile
70
70
  ignores these lines. Lines
71
- with more fields (`Title, Employer, City (dates)`) and titles containing a comma
72
- are not targeted and can leave the employer in place; date-anchored prose with
73
- the same shape can be flagged.
71
+ with parenthesized ranges additionally support arbitrary title prefixes,
72
+ comma-containing employers and titles, as described below. Date-anchored prose
73
+ with the same shape can be flagged.
74
74
 
75
75
  The gazetteer has only the cities/regions explicitly listed in that module,
76
76
  including Springfield and Riverton. It is not a worldwide geocoder. Location
@@ -243,16 +243,17 @@ The `documents` profile additionally recognizes:
243
243
  - Company/employer/workplace labels, capitalized company/holding/bank names,
244
244
  employment prose and year-range employer lines; residence/metro/district
245
245
  labels and contextual six-digit postal codes.
246
- - Russian street-plus-house/flat structures at line start or after
247
- `проживает по адресу`. Public-place prose such as
248
- `Музей находится на ул. Ленина, д. 5, кв. 12` stays unchanged. Public addresses
249
- presented alone have the same shape as personal addresses and may be flagged;
250
- inspect and review standalone public addresses before publication.
246
+ - Russian street-plus-house/flat structures, including inline addresses added
247
+ by #117. `Музей находится на ул. Ленина, д. 5, кв. 12` now removes the full
248
+ street/house/apartment span. Public addresses have the same shape as personal
249
+ addresses and may be flagged; inspect them before publication.
251
250
 
252
251
  Explicit limits accepted for the ambiguous examples in #82/#83:
253
- `Спасибо, Анна!`, `Анна, добрый день!`, `Менеджер Анна` and `Лютик, менеджер`
252
+ `Спасибо, Анна!`, `Анна, добрый день!` and `Менеджер Анна`
254
253
  remain unchanged without known values or additional context. Bare given names
255
254
  and unmarked employer/title fragments cannot reliably establish identity.
255
+ The reversed employer cue `Лютик, менеджер` now redacts `Лютик`, as requested
256
+ by #117. English `Worked at Romashka` now redacts the employer under #115.
256
257
  `зарплата 150 000 руб.` and `Salary: 150000` also remain unchanged: salary is
257
258
  outside the native document rules. Supply `knownPersonal` or an additional
258
259
  recognizer when these fields must be removed. Initial/name rules do not cover
@@ -376,7 +377,8 @@ Employer labels consume multi-word values to a comma or line/quote boundary.
376
377
  Dated CV entries additionally support compound titles, title parentheses/slashes,
377
378
  quoted group names, legal-form/location suffixes, empty/`null` range ends,
378
379
  multiple entries, employer-first role entries and pipe-separated entries.
379
- The supplemental grammar uses finite title cues and bounded entries. Undated
380
+ The parenthesized date-anchor scanner accepts arbitrary forward titles and
381
+ lead-ins. The reversed `Company, manager` layout retains finite role cues. Undated
380
382
  `Менеджер, Ромашка Лютик`, `- Ромашка Лютик 5 лет` and
381
383
  `Последнее место работы Ромашка Лютик` remain explicit ambiguous limits, as
382
384
  permitted by #94. Use known values or schema declarations for them.
@@ -385,8 +387,7 @@ Company prose recognizes `компании «…»`, `опыт в`, `Роль в
385
387
  capitalized lists after those cues, `опыт (Company Name)`, and names followed
386
388
  by `(2 года)`/`(3 года)`-style durations. Arbitrary uncued company words remain
387
389
  ambiguous; synchronous callers can supply `knownPersonal` lists or precomputed
388
- `findings`. Custom `detectors` callbacks are still used by `createSanitizer`
389
- only. No model or network service is required for these native rules.
390
+ `findings`. Array-returning custom `detectors` also run in synchronous methods; Promise-returning callbacks require `createSanitizer`. No model or network service is required for these native rules.
390
391
 
391
392
  `контакт:` and `пишите,` introduce domestic mobile numbers. A complete line
392
393
  consisting of an 11-digit `79…` or `89…` mobile shape is also sensitive. Arbitrary
@@ -402,3 +403,124 @@ redaction replaces the fragment instead of repeating an unchanged result until
402
403
 
403
404
  The [requirement inventory and research](case-studies/issue-100/README.md)
404
405
  records the complete scope and regression evidence.
406
+
407
+ ## Dated entries and optional prose (issues 104–106)
408
+
409
+ Parenthesized date anchors now delimit successive entries on the same line,
410
+ including arbitrary title words and lead-ins (`Стаж = 5 лет:`, `= 3 года,`).
411
+ Employers can contain comma suffixes, nested parentheses, slash-separated names,
412
+ legal forms and multiple words. A pipe after the employer preserves the industry
413
+ suffix. `Founder, consultant, Romashka (2019–2021)` and equivalent Russian entries
414
+ share the parser. The employer must start with a capital letter, a quote, or a
415
+ company-style digit/letter combination. **Lowercase employers remain a limit**:
416
+ `Менеджер, romashka (2019–2021)` / `Менеджер, ромашка (2019–2021)` stay, preserving
417
+ ambiguous controls such as `Встреча, понедельник (2019–2021)`.
418
+
419
+ `ГК` and `группа компаний` introduce quoted or unquoted group names. Employment
420
+ lists continue through repeated `, потом в` and `, затем в` items. Domestic
421
+ `79…`/`89…` mobile shapes after a comma work in Russian and English text; plain
422
+ `order`/`id` numbers stay. School detection also covers `(МГУ, 2012)`,
423
+ `Образование (МГУ)`, `в МГТУ им. Баумана` and `Обучение: Skillbox, 2020`.
424
+ The acronym vocabulary remains finite.
425
+
426
+ Weak organization experience/duration cues preserve a small explicit technology
427
+ set: Python, Go, SQL, Excel, Power BI, Kubernetes, PostgreSQL, Telegram and Scrum.
428
+ This is an ambiguity policy, not general recognition of every product; a real
429
+ employer with one of those names needs `knownPersonal` or a declared sensitive
430
+ field. Explicit private values/fields and stronger legal-form cues still win.
431
+
432
+ Document share-link detection also recognizes HTTP(S) URLs containing `/d/<id>`
433
+ for any host and nonempty id length. Ordinary website roots remain a limit.
434
+ Relative operational paths such as `{"location":"apps/api"}` stay; explicit
435
+ personal paths and credential rules remain active. Caller-sensitive field names
436
+ now use the same case/underscore/hyphen normalization as built-in document names.
437
+
438
+ For no-cue prose, enable [`properNames: 'capitalized'`](API.md#structural-prose-redaction).
439
+ It activates per value, defaults to Cyrillic script letters or structural data
440
+ triggers, and supports configurable Unicode scripts. It intentionally loses
441
+ capitalized ordinary words. Labels, lowercase words, dates, paths, JSON keys and
442
+ untriggered values are outside this additional rule. English untriggered prose,
443
+ lowercase names and a name immediately before a colon remain documented limits.
444
+ The API describes the interaction with existing detectors and placeholder modes.
445
+
446
+ The date-anchor scan visits each preceding entry once; the optional prose scanner
447
+ classifies maximal tokens in fixed passes, avoiding repeated suffix searches on
448
+ long unspaced input. This is not a latency guarantee for the whole pipeline.
449
+ [The requirement inventory](case-studies/issue-106/README.md) records alternatives,
450
+ research, regression evidence and reproducible bounded performance measurements.
451
+
452
+ ## Stored-log policy and expanded document catalog
453
+
454
+ The documents profile now covers the exact positive/control rows of issues
455
+ 115 and 117: spaced/obfuscated contacts, Discord/MAX handles, additional
456
+ personal-link routes, birthday/age cues, military/VIN identifiers, employer and
457
+ school layouts, patronymics/initials/mixed scripts, complete street/house spans,
458
+ regions, citizenship and plates. Regression tests compare complete text output
459
+ and JSON/JSONL output in both engines. `ОГРНИП` without a value, operational logs,
460
+ work durations, weekdays and technologies remain unchanged. Bare `Сергей`,
461
+ `Смирнова`, `Смирновой`, `СПб`, `Питер` and `Вчера Анна написала` deliberately remain
462
+ ambiguous without explicit known values, the optional recognizer or aggressive
463
+ proper-name mode. Romashka Inc keeps its existing legal-form treatment.
464
+
465
+ `strict: true` independently opts into personal attributes (gender, family,
466
+ ethnicity, religion, health, criminal/political status) and bare dates, ages,
467
+ passport/mobile/postal shapes. It is intended for stored personal prose and can
468
+ remove ordinary uses of these words. It preserves ISO timestamps, work durations,
469
+ unit-bearing request timings and `Дети: нет`. In JSON/JSONL, numeric primitives
470
+ stay numeric, and `structuralFields` suppresses strict findings in declared
471
+ string metadata. Credentials/known values still take priority. Strict is off by
472
+ default and does not claim to identify every sensitive attribute or validate
473
+ that every date/number belongs to a real person.
474
+
475
+ ## Optional offline recognition measurements
476
+
477
+ The `/ner` entry uses the separately installed optional
478
+ [NLP.js](https://github.com/axa-group/nlp.js) regex extractor, finite multilingual
479
+ name/place dictionaries and bounded contextual rules. It has synchronous
480
+ inference after `await createNerDetector(...)`. There is no analysis-time network,
481
+ external service, Python process or model download. The core import graph never
482
+ imports `/ner` or NLP.js, so the optional component has no runtime or loading cost
483
+ when disabled. This is symbolic recognition, not general statistical NER; unknown
484
+ cue-free surnames, schools, employers and locations can be missed.
485
+
486
+ [Test set](../experiments/issue-118/ner-corpus.json): 32 synthetic sentences,
487
+ including all six requested positives, eight benign controls and eight challenge
488
+ misses. Exact type and UTF-16 start/end must all match. **All** rows, including
489
+ challenge rows, contribute to these scores. Each language/type has two true
490
+ positives, zero false positives and one false negative:
491
+
492
+ | Language | Type | Precision | Recall | TP / FP / FN |
493
+ | -------- | ------------ | --------- | ------ | ------------ |
494
+ | ru | PERSON | 100% | 66.7% | 2 / 0 / 1 |
495
+ | ru | ORGANIZATION | 100% | 66.7% | 2 / 0 / 1 |
496
+ | ru | EDUCATION | 100% | 66.7% | 2 / 0 / 1 |
497
+ | ru | LOCATION | 100% | 66.7% | 2 / 0 / 1 |
498
+ | en | PERSON | 100% | 66.7% | 2 / 0 / 1 |
499
+ | en | ORGANIZATION | 100% | 66.7% | 2 / 0 / 1 |
500
+ | en | EDUCATION | 100% | 66.7% | 2 / 0 / 1 |
501
+ | en | LOCATION | 100% | 66.7% | 2 / 0 / 1 |
502
+
503
+ These small authored regression scores do not estimate accuracy on arbitrary
504
+ user documents. The test includes explicit misses such as `Zephyr Quill`,
505
+ `Asterion expanded rapidly`, `ETHZ` and `Reykjavik` so dictionary limits remain
506
+ visible. Acceptance rows also test both sanitizers and unchanged controls.
507
+
508
+ On Node v26.13.0, Linux x64, five bounded groups, the median warm detector cost
509
+ was **0.0434 ms per 1 KiB** of ASCII employer prose; module loading plus factory
510
+ initialization took **48.6 ms** once. Cost excludes the sanitizer's other
511
+ recognizers and varies with text and hardware. No latency guarantee is implied.
512
+
513
+ ## Compiled known-value measurements
514
+
515
+ On the same host and 1 KiB operational record, median complete `sanitize` cost
516
+ was **2.80 ms** without a list, **3.30 ms** with 500 compiled values and **8.56 ms**
517
+ with a plain array. A direct 500-value matching pass cost **0.73 ms**. Validation
518
+ cost was **0.00416 ms** without a list and **0.00356 ms** compiled (the difference
519
+ is measurement noise), below the allowed 10% of the no-list call, excluding
520
+ matching. Compilation skips per-record entry validation, normalization and regex
521
+ construction; matching itself still scales with list length and pipeline passes.
522
+
523
+ Reproduce with `node --max-old-space-size=384 experiments/issue-118/measure.mjs`.
524
+ The [raw results](case-studies/issue-118/measurements.json) include environment,
525
+ exact-span counts and measurement method. This fixed experiment uses no large
526
+ corpus, unbounded input, parallel builds or external analysis service.
package/docs/RULES.md ADDED
@@ -0,0 +1,403 @@
1
+ # Stable finding rule identifiers
2
+
3
+ Use keys from the exported frozen `builtInRules` list in `rules: {id: false}`.
4
+ Unknown ids fail with ERR_CONFIG. Credential and mixed encoded/scanner rules
5
+ require `allowCredentialDisable: true`. A disabled rule does not suppress
6
+ another rule's overlapping finding. Rules gate automatic built-in findings;
7
+ explicit findings and custom callbacks retain caller policy. See [API](API.md#rule-type-and-field-policy).
8
+
9
+ Regenerate with `node experiments/issue-118/generate-rule-catalog.mjs`.
10
+
11
+ - `age-context`
12
+ - `anthropic`
13
+ - `authorization`
14
+ - `aws`
15
+ - `birth-context`
16
+ - `birth-year`
17
+ - `booking-context`
18
+ - `booking-filename`
19
+ - `catalog/aws`
20
+ - `catalog/azure`
21
+ - `catalog/google`
22
+ - `cli-password`
23
+ - `context`
24
+ - `cookie`
25
+ - `databricks`
26
+ - `detect-secrets`
27
+ - `digitalocean`
28
+ - `discord`
29
+ - `document-age-unit`
30
+ - `document-at-employer`
31
+ - `document-bank-suffix`
32
+ - `document-birthday`
33
+ - `document-citizenship`
34
+ - `document-email-obfuscation`
35
+ - `document-email-spaces`
36
+ - `document-employer-dash`
37
+ - `document-employer-prefix`
38
+ - `document-industry`
39
+ - `document-introduction`
40
+ - `document-legal-lowercase`
41
+ - `document-local-phone`
42
+ - `document-messenger`
43
+ - `document-military-id`
44
+ - `document-mixed-name`
45
+ - `document-mobile-bare`
46
+ - `document-mobile-comma`
47
+ - `document-mobile-context`
48
+ - `document-msu`
49
+ - `document-patronymic`
50
+ - `document-personal-path`
51
+ - `document-phone`
52
+ - `document-phone-label`
53
+ - `document-propagation`
54
+ - `document-region`
55
+ - `document-resume-link`
56
+ - `document-reversed-role`
57
+ - `document-role`
58
+ - `document-role-employer`
59
+ - `document-school-context`
60
+ - `document-school-year`
61
+ - `document-share-link`
62
+ - `document-share-path`
63
+ - `document-social-link`
64
+ - `document-spaced-mobile`
65
+ - `document-street`
66
+ - `document-street-complete`
67
+ - `document-unfamiliar-name`
68
+ - `document-uppercase-initials`
69
+ - `document-vehicle-plate`
70
+ - `document-vin`
71
+ - `education-acronym`
72
+ - `education-bracket`
73
+ - `education-label`
74
+ - `education-named-acronym`
75
+ - `education-russian`
76
+ - `education-training`
77
+ - `education-type`
78
+ - `email`
79
+ - `employer-label`
80
+ - `employer-prose`
81
+ - `employer-years`
82
+ - `encoded`
83
+ - `encoded-engine`
84
+ - `encoded-limit`
85
+ - `entity-format`
86
+ - `entropy`
87
+ - `firebase`
88
+ - `gender-context`
89
+ - `github`
90
+ - `gitlab`
91
+ - `gitlab-runner`
92
+ - `gitleaks/1password-secret-key`
93
+ - `gitleaks/1password-service-account-token`
94
+ - `gitleaks/adafruit-api-key`
95
+ - `gitleaks/adobe-client-id`
96
+ - `gitleaks/adobe-client-secret`
97
+ - `gitleaks/age-secret-key`
98
+ - `gitleaks/airtable-api-key`
99
+ - `gitleaks/airtable-personnal-access-token`
100
+ - `gitleaks/algolia-api-key`
101
+ - `gitleaks/alibaba-access-key-id`
102
+ - `gitleaks/alibaba-secret-key`
103
+ - `gitleaks/anthropic-admin-api-key`
104
+ - `gitleaks/anthropic-api-key`
105
+ - `gitleaks/artifactory-api-key`
106
+ - `gitleaks/artifactory-reference-token`
107
+ - `gitleaks/asana-client-id`
108
+ - `gitleaks/asana-client-secret`
109
+ - `gitleaks/atlassian-api-token`
110
+ - `gitleaks/authress-service-client-access-key`
111
+ - `gitleaks/aws-access-token`
112
+ - `gitleaks/aws-amazon-bedrock-api-key-long-lived`
113
+ - `gitleaks/aws-amazon-bedrock-api-key-short-lived`
114
+ - `gitleaks/azure-ad-client-secret`
115
+ - `gitleaks/beamer-api-token`
116
+ - `gitleaks/bitbucket-client-id`
117
+ - `gitleaks/bitbucket-client-secret`
118
+ - `gitleaks/bittrex-access-key`
119
+ - `gitleaks/bittrex-secret-key`
120
+ - `gitleaks/cisco-meraki-api-key`
121
+ - `gitleaks/clickhouse-cloud-api-secret-key`
122
+ - `gitleaks/clojars-api-token`
123
+ - `gitleaks/cloudflare-api-key`
124
+ - `gitleaks/cloudflare-global-api-key`
125
+ - `gitleaks/cloudflare-origin-ca-key`
126
+ - `gitleaks/codecov-access-token`
127
+ - `gitleaks/cohere-api-token`
128
+ - `gitleaks/coinbase-access-token`
129
+ - `gitleaks/confluent-access-token`
130
+ - `gitleaks/confluent-secret-key`
131
+ - `gitleaks/contentful-delivery-api-token`
132
+ - `gitleaks/curl-auth-header`
133
+ - `gitleaks/curl-auth-user`
134
+ - `gitleaks/databricks-api-token`
135
+ - `gitleaks/datadog-access-token`
136
+ - `gitleaks/defined-networking-api-token`
137
+ - `gitleaks/digitalocean-access-token`
138
+ - `gitleaks/digitalocean-pat`
139
+ - `gitleaks/digitalocean-refresh-token`
140
+ - `gitleaks/discord-api-token`
141
+ - `gitleaks/discord-client-id`
142
+ - `gitleaks/discord-client-secret`
143
+ - `gitleaks/doppler-api-token`
144
+ - `gitleaks/droneci-access-token`
145
+ - `gitleaks/dropbox-api-token`
146
+ - `gitleaks/dropbox-long-lived-api-token`
147
+ - `gitleaks/dropbox-short-lived-api-token`
148
+ - `gitleaks/duffel-api-token`
149
+ - `gitleaks/dynatrace-api-token`
150
+ - `gitleaks/easypost-api-token`
151
+ - `gitleaks/easypost-test-api-token`
152
+ - `gitleaks/etsy-access-token`
153
+ - `gitleaks/facebook-access-token`
154
+ - `gitleaks/facebook-page-access-token`
155
+ - `gitleaks/facebook-secret`
156
+ - `gitleaks/fastly-api-token`
157
+ - `gitleaks/finicity-api-token`
158
+ - `gitleaks/finicity-client-secret`
159
+ - `gitleaks/finnhub-access-token`
160
+ - `gitleaks/flickr-access-token`
161
+ - `gitleaks/flutterwave-encryption-key`
162
+ - `gitleaks/flutterwave-public-key`
163
+ - `gitleaks/flutterwave-secret-key`
164
+ - `gitleaks/flyio-access-token`
165
+ - `gitleaks/frameio-api-token`
166
+ - `gitleaks/freemius-secret-key`
167
+ - `gitleaks/freshbooks-access-token`
168
+ - `gitleaks/gcp-api-key`
169
+ - `gitleaks/generic-api-key`
170
+ - `gitleaks/github-app-token`
171
+ - `gitleaks/github-fine-grained-pat`
172
+ - `gitleaks/github-oauth`
173
+ - `gitleaks/github-pat`
174
+ - `gitleaks/github-refresh-token`
175
+ - `gitleaks/gitlab-cicd-job-token`
176
+ - `gitleaks/gitlab-deploy-token`
177
+ - `gitleaks/gitlab-feature-flag-client-token`
178
+ - `gitleaks/gitlab-feed-token`
179
+ - `gitleaks/gitlab-incoming-mail-token`
180
+ - `gitleaks/gitlab-kubernetes-agent-token`
181
+ - `gitleaks/gitlab-oauth-app-secret`
182
+ - `gitleaks/gitlab-pat`
183
+ - `gitleaks/gitlab-pat-routable`
184
+ - `gitleaks/gitlab-ptt`
185
+ - `gitleaks/gitlab-rrt`
186
+ - `gitleaks/gitlab-runner-authentication-token`
187
+ - `gitleaks/gitlab-runner-authentication-token-routable`
188
+ - `gitleaks/gitlab-scim-token`
189
+ - `gitleaks/gitlab-session-cookie`
190
+ - `gitleaks/gitter-access-token`
191
+ - `gitleaks/gocardless-api-token`
192
+ - `gitleaks/grafana-api-key`
193
+ - `gitleaks/grafana-cloud-api-token`
194
+ - `gitleaks/grafana-service-account-token`
195
+ - `gitleaks/harness-api-key`
196
+ - `gitleaks/hashicorp-tf-api-token`
197
+ - `gitleaks/hashicorp-tf-password`
198
+ - `gitleaks/heroku-api-key`
199
+ - `gitleaks/heroku-api-key-v2`
200
+ - `gitleaks/hubspot-api-key`
201
+ - `gitleaks/huggingface-access-token`
202
+ - `gitleaks/huggingface-organization-api-token`
203
+ - `gitleaks/infracost-api-token`
204
+ - `gitleaks/intercom-api-key`
205
+ - `gitleaks/intra42-client-secret`
206
+ - `gitleaks/jfrog-api-key`
207
+ - `gitleaks/jfrog-identity-token`
208
+ - `gitleaks/jwt`
209
+ - `gitleaks/jwt-base64`
210
+ - `gitleaks/kraken-access-token`
211
+ - `gitleaks/kubernetes-secret-yaml`
212
+ - `gitleaks/kucoin-access-token`
213
+ - `gitleaks/kucoin-secret-key`
214
+ - `gitleaks/launchdarkly-access-token`
215
+ - `gitleaks/linear-api-key`
216
+ - `gitleaks/linear-client-secret`
217
+ - `gitleaks/linkedin-client-id`
218
+ - `gitleaks/linkedin-client-secret`
219
+ - `gitleaks/lob-api-key`
220
+ - `gitleaks/lob-pub-api-key`
221
+ - `gitleaks/looker-client-id`
222
+ - `gitleaks/looker-client-secret`
223
+ - `gitleaks/mailchimp-api-key`
224
+ - `gitleaks/mailgun-private-api-token`
225
+ - `gitleaks/mailgun-pub-key`
226
+ - `gitleaks/mailgun-signing-key`
227
+ - `gitleaks/mapbox-api-token`
228
+ - `gitleaks/mattermost-access-token`
229
+ - `gitleaks/maxmind-license-key`
230
+ - `gitleaks/messagebird-api-token`
231
+ - `gitleaks/messagebird-client-id`
232
+ - `gitleaks/microsoft-teams-webhook`
233
+ - `gitleaks/netlify-access-token`
234
+ - `gitleaks/new-relic-browser-api-token`
235
+ - `gitleaks/new-relic-insert-key`
236
+ - `gitleaks/new-relic-user-api-id`
237
+ - `gitleaks/new-relic-user-api-key`
238
+ - `gitleaks/notion-api-token`
239
+ - `gitleaks/npm-access-token`
240
+ - `gitleaks/nuget-config-password`
241
+ - `gitleaks/nytimes-access-token`
242
+ - `gitleaks/octopus-deploy-api-key`
243
+ - `gitleaks/okta-access-token`
244
+ - `gitleaks/openai-api-key`
245
+ - `gitleaks/openshift-user-token`
246
+ - `gitleaks/perplexity-api-key`
247
+ - `gitleaks/pkcs12-file`
248
+ - `gitleaks/plaid-api-token`
249
+ - `gitleaks/plaid-client-id`
250
+ - `gitleaks/plaid-secret-key`
251
+ - `gitleaks/planetscale-api-token`
252
+ - `gitleaks/planetscale-oauth-token`
253
+ - `gitleaks/planetscale-password`
254
+ - `gitleaks/postman-api-token`
255
+ - `gitleaks/prefect-api-token`
256
+ - `gitleaks/private-key`
257
+ - `gitleaks/privateai-api-token`
258
+ - `gitleaks/pulumi-api-token`
259
+ - `gitleaks/pypi-upload-token`
260
+ - `gitleaks/rapidapi-access-token`
261
+ - `gitleaks/readme-api-token`
262
+ - `gitleaks/rubygems-api-token`
263
+ - `gitleaks/scalingo-api-token`
264
+ - `gitleaks/sendbird-access-id`
265
+ - `gitleaks/sendbird-access-token`
266
+ - `gitleaks/sendgrid-api-token`
267
+ - `gitleaks/sendinblue-api-token`
268
+ - `gitleaks/sentry-access-token`
269
+ - `gitleaks/sentry-org-token`
270
+ - `gitleaks/sentry-user-token`
271
+ - `gitleaks/settlemint-application-access-token`
272
+ - `gitleaks/settlemint-personal-access-token`
273
+ - `gitleaks/settlemint-service-access-token`
274
+ - `gitleaks/shippo-api-token`
275
+ - `gitleaks/shopify-access-token`
276
+ - `gitleaks/shopify-custom-access-token`
277
+ - `gitleaks/shopify-private-app-access-token`
278
+ - `gitleaks/shopify-shared-secret`
279
+ - `gitleaks/sidekiq-secret`
280
+ - `gitleaks/sidekiq-sensitive-url`
281
+ - `gitleaks/slack-app-token`
282
+ - `gitleaks/slack-bot-token`
283
+ - `gitleaks/slack-config-access-token`
284
+ - `gitleaks/slack-config-refresh-token`
285
+ - `gitleaks/slack-legacy-bot-token`
286
+ - `gitleaks/slack-legacy-token`
287
+ - `gitleaks/slack-legacy-workspace-token`
288
+ - `gitleaks/slack-user-token`
289
+ - `gitleaks/slack-webhook-url`
290
+ - `gitleaks/snyk-api-token`
291
+ - `gitleaks/sonar-api-token`
292
+ - `gitleaks/sourcegraph-access-token`
293
+ - `gitleaks/square-access-token`
294
+ - `gitleaks/squarespace-access-token`
295
+ - `gitleaks/stripe-access-token`
296
+ - `gitleaks/sumologic-access-id`
297
+ - `gitleaks/sumologic-access-token`
298
+ - `gitleaks/telegram-bot-api-token`
299
+ - `gitleaks/travisci-access-token`
300
+ - `gitleaks/twilio-api-key`
301
+ - `gitleaks/twitch-api-token`
302
+ - `gitleaks/twitter-access-secret`
303
+ - `gitleaks/twitter-access-token`
304
+ - `gitleaks/twitter-api-key`
305
+ - `gitleaks/twitter-api-secret`
306
+ - `gitleaks/twitter-bearer-token`
307
+ - `gitleaks/typeform-api-token`
308
+ - `gitleaks/vault-batch-token`
309
+ - `gitleaks/vault-service-token`
310
+ - `gitleaks/yandex-access-token`
311
+ - `gitleaks/yandex-api-key`
312
+ - `gitleaks/yandex-aws-access-token`
313
+ - `gitleaks/zendesk-secret-key`
314
+ - `google`
315
+ - `grafana`
316
+ - `home-path`
317
+ - `huggingface`
318
+ - `iban-checksum`
319
+ - `identity-number`
320
+ - `international-phone`
321
+ - `ipv4`
322
+ - `ipv6`
323
+ - `jwt`
324
+ - `known-personal`
325
+ - `known-secret`
326
+ - `label`
327
+ - `linear`
328
+ - `location-gazetteer`
329
+ - `location-label`
330
+ - `location-residence`
331
+ - `luhn`
332
+ - `mac`
333
+ - `mailchimp`
334
+ - `messenger-handle`
335
+ - `messenger-label`
336
+ - `mrz`
337
+ - `name-case`
338
+ - `name-document`
339
+ - `name-gazetteer`
340
+ - `name-identity`
341
+ - `name-initials`
342
+ - `name-title`
343
+ - `name-variant`
344
+ - `national-format`
345
+ - `national-phone`
346
+ - `ner/offline`
347
+ - `netrc`
348
+ - `notion`
349
+ - `npm`
350
+ - `nuget`
351
+ - `numbered-school`
352
+ - `obfuscated-email`
353
+ - `openai`
354
+ - `organization-context`
355
+ - `organization-continuation`
356
+ - `organization-core`
357
+ - `organization-cue`
358
+ - `organization-duration`
359
+ - `organization-experience`
360
+ - `organization-experience-prose`
361
+ - `organization-group`
362
+ - `organization-group-cue`
363
+ - `organization-legal-form`
364
+ - `passport-format`
365
+ - `passport-issuer`
366
+ - `passport-series`
367
+ - `personal-profile-url`
368
+ - `phone-context`
369
+ - `postal-address`
370
+ - `postal-code`
371
+ - `presidio`
372
+ - `private-key`
373
+ - `profile-attribute`
374
+ - `profile-prose`
375
+ - `proper-name-prose`
376
+ - `putty-private-key`
377
+ - `pypi`
378
+ - `quoted-entropy`
379
+ - `residence-prose`
380
+ - `school-acronym-context`
381
+ - `secretlint`
382
+ - `sendgrid`
383
+ - `shopify`
384
+ - `slack`
385
+ - `square`
386
+ - `strict-age`
387
+ - `strict-attribute`
388
+ - `strict-date`
389
+ - `strict-number`
390
+ - `stripe`
391
+ - `tel-uri`
392
+ - `telegram`
393
+ - `ticket-context`
394
+ - `twilio`
395
+ - `url-userinfo`
396
+ - `us-ssn`
397
+ - `vault`
398
+ - `vercel`
399
+ - `visa-context`
400
+ - `vocabulary-cue`
401
+ - `vocabulary-role`
402
+ - `webhook`
403
+ - `wordpress`
@@ -0,0 +1,23 @@
1
+ import { compileKnown, createSyncSanitizer } from '../src/index.js';
2
+
3
+ // Reuse caller-owned compilation in a synchronous logging hook.
4
+ const known = compileKnown([{ type: 'ORGANIZATION', value: 'Ромашка' }]);
5
+ const loggerPolicy = createSyncSanitizer({
6
+ profile: 'documents',
7
+ knownPersonal: known,
8
+ vocabulary: { roles: ['маркетолог'] },
9
+ structured: 'json',
10
+ allowedFields: ['level', 'msg'],
11
+ fields: { location: 'scan' },
12
+ transformations: { ORGANIZATION: () => 'PrivateCompany' },
13
+ });
14
+ console.log(
15
+ loggerPolicy.sanitize(
16
+ JSON.stringify({
17
+ level: 'info',
18
+ msg: 'работал в Ромашке',
19
+ privateNote: 'caller-private text',
20
+ count: 1,
21
+ })
22
+ ).text
23
+ );
@@ -0,0 +1,16 @@
1
+ import { sanitize } from '../src/index.js';
2
+
3
+ // Synthetic mixed records: only the prose string activates the extra rule.
4
+ const source = JSON.stringify({
5
+ note: 'Работал в Ромашке менеджером по продажам.',
6
+ operational: 'Connection to Postgres lost, retrying',
7
+ count: 42,
8
+ });
9
+ console.log(
10
+ sanitize(source, {
11
+ profile: 'documents',
12
+ properNames: 'capitalized',
13
+ scripts: ['Cyrillic'],
14
+ structured: 'json',
15
+ }).text
16
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-foundation/sensitive-data-sanitizer",
3
- "version": "0.17.1",
3
+ "version": "0.19.0",
4
4
  "description": "Offline sensitive-text sanitization with credential, multilingual personal-data and scanner union detection",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -24,7 +24,10 @@
24
24
  "LICENSE",
25
25
  "README.md",
26
26
  "examples/shared-tool-logs.mjs",
27
- "examples/reversible-placeholders.mjs"
27
+ "examples/reversible-placeholders.mjs",
28
+ "examples/prose-logs.mjs",
29
+ "docs/RULES.md",
30
+ "examples/custom-log-policy.mjs"
28
31
  ],
29
32
  "exports": {
30
33
  ".": {
@@ -39,6 +42,10 @@
39
42
  "types": "./src/legacy.d.ts",
40
43
  "browser": "./src/arithmetic.js",
41
44
  "import": "./src/legacy.js"
45
+ },
46
+ "./ner": {
47
+ "types": "./src/ner.d.ts",
48
+ "import": "./src/ner.js"
42
49
  }
43
50
  },
44
51
  "scripts": {
@@ -95,6 +102,7 @@
95
102
  "devDependencies": {
96
103
  "@changesets/cli": "^3.0.3",
97
104
  "@eslint/js": "^10.0.1",
105
+ "@nlpjs/ner": "4.27.0",
98
106
  "browser-commander": "0.34.1",
99
107
  "cmd-shim": "^9.0.2",
100
108
  "command-stream": "2.6.0",
@@ -150,5 +158,13 @@
150
158
  },
151
159
  "overrides": {
152
160
  "minimatch@>=7 <11": "^11.0.5"
161
+ },
162
+ "peerDependencies": {
163
+ "@nlpjs/ner": "^4.27.0"
164
+ },
165
+ "peerDependenciesMeta": {
166
+ "@nlpjs/ner": {
167
+ "optional": true
168
+ }
153
169
  }
154
170
  }