@link-foundation/sensitive-data-sanitizer 0.19.3 → 0.21.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/CHANGELOG.md +12 -0
- package/README.md +22 -2
- package/bin/sensitive-data-sanitizer.js +50 -28
- package/docs/API.md +355 -16
- package/docs/COVERAGE.md +19 -9
- package/docs/RULES.md +218 -0
- package/examples/second-pass-placeholders.mjs +28 -0
- package/package.json +12 -3
- package/src/allow-list.js +93 -0
- package/src/confidence.js +24 -9
- package/src/context.js +287 -40
- package/src/credential-spans.js +180 -0
- package/src/credentials-entry.d.ts +23 -0
- package/src/credentials-entry.js +62 -0
- package/src/credentials.js +51 -0
- package/src/crypto-addresses.js +74 -0
- package/src/detection.js +87 -41
- package/src/document-person-cues.js +1 -1
- package/src/encoded.js +137 -51
- package/src/encrypt.js +95 -0
- package/src/engines.js +33 -10
- package/src/entities.js +4 -3
- package/src/exempt.js +72 -0
- package/src/fake-memory.js +32 -0
- package/src/fake.js +33 -29
- package/src/features.js +29 -0
- package/src/files.js +57 -12
- package/src/gitleaks.js +82 -27
- package/src/html-tags.js +8 -0
- package/src/identifier-detector.js +36 -0
- package/src/identity-types.js +63 -0
- package/src/identity.js +3 -47
- package/src/index.d.ts +157 -3
- package/src/index.js +13 -0
- package/src/known.js +33 -15
- package/src/national-checks.js +209 -0
- package/src/national.js +310 -92
- package/src/pattern-detector.js +219 -0
- package/src/personal-context.js +3 -2
- package/src/personal-evidence.js +233 -0
- package/src/personal-features.js +39 -0
- package/src/personal.js +66 -10
- package/src/placeholder-types.js +154 -4
- package/src/placeholders.js +127 -45
- package/src/projection.js +78 -26
- package/src/proper-names.js +48 -5
- package/src/prose-words.js +120 -0
- package/src/record-worker.js +1 -0
- package/src/rule-catalog.js +426 -0
- package/src/rules.js +8 -0
- package/src/sanitizer.js +133 -23
- package/src/sarif.js +127 -0
- package/src/stream-worker.js +1 -0
- package/src/strict-attributes.js +48 -4
- package/src/strict.js +1 -1
- package/src/structured.js +2 -0
- package/src/token-checksum.js +49 -0
- package/src/transforms.js +146 -33
- package/src/vendor/betterleaks/LICENSE +21 -0
- package/src/vendor/betterleaks/provenance.json +11 -0
- package/src/vendor/betterleaks/rules-0.json +463 -0
- package/src/vendor/betterleaks/rules-1.json +454 -0
- package/src/vendor/betterleaks/rules-2.json +461 -0
- package/src/vendor/betterleaks/rules-3.json +455 -0
- package/src/vendor/betterleaks/rules-4.json +483 -0
- package/src/vendor/betterleaks/rules-5.json +35 -0
- package/src/vendor/entities/aws.json +1 -1
- package/src/vendor/entities/azure.json +6 -2
- package/src/vendor/entities/google.json +2 -1
- package/src/vocabulary.js +27 -1
- package/src/worker-options.js +1 -0
package/docs/API.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Library and detector contract
|
|
2
2
|
|
|
3
|
-
Import the
|
|
3
|
+
Import the root entry with `import` or `require`; complete declarations are in `src/index.d.ts`. Inputs are strings, offsets are UTF-16 code units, starts are zero-based, and ends are exclusive. Returned line and column numbers are one-based; columns also count UTF-16 units.
|
|
4
4
|
|
|
5
5
|
## Methods
|
|
6
6
|
|
|
@@ -16,9 +16,38 @@ Import the ESM root entry; complete declarations are in `src/index.d.ts`. Inputs
|
|
|
16
16
|
| `createSyncSanitizer(options)` | Reusable synchronous `inspect` / `sanitize` with the same policies |
|
|
17
17
|
| `compileKnown(...lists)` | Copy, freeze, merge and precompile caller-known personal values |
|
|
18
18
|
| `auditGitHistory(repository, options)` | Read-only audit of local reachable text objects |
|
|
19
|
+
| `decrypt(text, key, options)` | Restore values from `[ENCRYPTED:TYPE:…]` tokens written by the `encrypt` transformation |
|
|
20
|
+
| `createPatternDetector(specs, opts)` | Declarative regex, deny-list, hotword and validator detector |
|
|
21
|
+
| `createIdentifierDetector(rules)` | Detector for caller-supplied national identifier rules (`nationalRules` shape) |
|
|
22
|
+
| `toSarif(report, options)` | Convert a scan report or findings to SARIF 2.1.0 without values |
|
|
19
23
|
|
|
20
24
|
`redactions` counts merged replacement spans. `findings` can contain several detector hits for one replacement. Findings have `{start, end, type, category, rule, line, column}`; no text, quote, match, entropy score, provider details, or raw diagnostic is copied. The optional `strict-attributes` debug event reports only detected and suppressed match counts. Debugging is off by default; `debug(event)` receives value-free counts, rule timings, residual rounds and registry event metadata (see `DebugEvent` in the TypeScript declarations).
|
|
21
25
|
|
|
26
|
+
### Credentials-only entry
|
|
27
|
+
|
|
28
|
+
`@link-foundation/sensitive-data-sanitizer/credentials` exports `inspect`,
|
|
29
|
+
`sanitize`, `redact`, `createSanitizer`, `createSyncSanitizer`, `REDACTED`,
|
|
30
|
+
`entropy`, `builtInRules`, `tokenChecksum`, `tokenWithChecksum` and the
|
|
31
|
+
`knownSecretsFrom*` helpers. It always runs with `credentialsOnly: true` and
|
|
32
|
+
does not load the personal-data detectors, faker or libphonenumber, so it starts
|
|
33
|
+
about three times faster than the root entry (about 0.25 s against 0.75 s on
|
|
34
|
+
Node.js 26). Use it for loggers that must mask every line from the first write:
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
const {
|
|
38
|
+
createSyncSanitizer,
|
|
39
|
+
knownSecretsFromEnv,
|
|
40
|
+
} = require('@link-foundation/sensitive-data-sanitizer/credentials');
|
|
41
|
+
const mask = createSyncSanitizer({ knownSecrets: knownSecretsFromEnv() });
|
|
42
|
+
console.log(mask.sanitize('Authorization: Bearer abc123def456ghi789').text);
|
|
43
|
+
// Authorization: Bearer [REDACTED]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Options that need the personal-data modules, `credentialsOnly: false`,
|
|
47
|
+
`mode: 'fake'` and `properNames`, fail with `ERR_CONFIG`. Known personal values
|
|
48
|
+
(`knownPersonal`) still work. Both entries offer `import`, `require` (Node.js
|
|
49
|
+
22.12 or later) and `default` export conditions.
|
|
50
|
+
|
|
22
51
|
## Options
|
|
23
52
|
|
|
24
53
|
### Replace only known values
|
|
@@ -67,6 +96,31 @@ Unicode letters, marks, digits and underscores bound those tokens. Ordinary
|
|
|
67
96
|
technical names such as GPT-4, ISO-9001 and Windows-10 are not reserved. The map's format cannot change
|
|
68
97
|
on reuse. A malformed map or ambiguous exact restoration blocks output.
|
|
69
98
|
|
|
99
|
+
`existing: 'keep'` on the placeholder transformation accepts input that already
|
|
100
|
+
contains tokens of the chosen namespace, for example the output of an earlier
|
|
101
|
+
pass or a model answer that quotes tokens. Those tokens stay as they are and are
|
|
102
|
+
never replaced, faked or treated as names; new values of a type are numbered
|
|
103
|
+
after the highest kept number of that type:
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
const policy = { transformation: { mode: 'placeholder', existing: 'keep' } };
|
|
107
|
+
sanitize('Person-1 встретил Анну Смирнову', policy).text;
|
|
108
|
+
// Person-1 встретил Person-2
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
A second pass over sanitized text is then a fixed point. The default
|
|
112
|
+
`existing: 'error'` keeps the `ERR_PLACEHOLDER_COLLISION` check; other values and
|
|
113
|
+
a mix with per-type `transformations` are `ERR_CONFIG`. A kept token restores to
|
|
114
|
+
itself, so the map still reconstructs every newly replaced value exactly.
|
|
115
|
+
|
|
116
|
+
`reservedPlaceholderTypes()` returns the sorted, frozen list of every type a
|
|
117
|
+
built-in rule can turn into a token. `isPlaceholderToken(text, {format?, types?})`
|
|
118
|
+
checks whether `text` is one whole token of the namespace (`Person-1`,
|
|
119
|
+
`UsBankNumber-12`, `<PERSON_3>` with `format: '<{TYPE}_{n}>'`); `types` adds
|
|
120
|
+
custom types such as those of `createPatternDetector`. Both are also exported by
|
|
121
|
+
the credentials-only entry. The namespace pattern is compiled once per format and
|
|
122
|
+
cached.
|
|
123
|
+
|
|
70
124
|
`createRestoreMap({format?})` creates an empty reusable map. Pass a map as
|
|
71
125
|
`transformation.map` to continue a corpus; the operation updates that same
|
|
72
126
|
object. A `createSanitizer()` engine also keeps its registry across calls.
|
|
@@ -147,6 +201,13 @@ JSON keys remain unchanged by automatic personal detection in all transformation
|
|
|
147
201
|
modes. Explicit known values and credentials retain priority. Parseability and
|
|
148
202
|
duplicate-key checks remain enforced. Plain redact/fake policies retain their
|
|
149
203
|
existing metadata/key behavior. Use `structured` for valid structured input.
|
|
204
|
+
A whole JSON value replaced by a structured rule (`sensitiveFields`, or a
|
|
205
|
+
personal field such as `age` replaced with its JSON type) becomes one JSON
|
|
206
|
+
string token typed by its field: `{"notes":"любой текст"}` reads
|
|
207
|
+
`{"notes":"Sensitive-1"}` and `{"age":34}` reads `{"age":"Age-1"}`. Such findings
|
|
208
|
+
carry `valueType` when the field has a personal type. `restore()` brings back
|
|
209
|
+
the original JSON value, including numbers and objects. Other modes keep
|
|
210
|
+
`"[REDACTED]"`.
|
|
150
211
|
The normal encoding limits and credential redaction policy continue to apply.
|
|
151
212
|
|
|
152
213
|
CLI: `redact FILE --placeholder --map private.json` creates or continues a map;
|
|
@@ -165,7 +226,7 @@ new document recognizers; `documentEntities: false` turns them off explicitly.
|
|
|
165
226
|
| `scripts` | `['Cyrillic']`; Unicode Script aliases that activate the prose rule; `[]` disables only script activation |
|
|
166
227
|
| `profile` | Off; `'publication'` sets a 0.3 threshold and identity masking; `'documents'` enables document entities |
|
|
167
228
|
| `knownOnly` | `false`; disable all automatic detection (including credentials, Secretlint and custom detectors); use explicit findings, known values and reused map identities |
|
|
168
|
-
| `knownSecrets` | `[]`; exact literal credential values, regardless of length or format
|
|
229
|
+
| `knownSecrets` | `[]`; exact literal credential values, regardless of length or format, plus their standalone hex (either case) and base64/base64url spellings |
|
|
169
230
|
| `knownPersonal` | `[]`; `{type, value}` values with whole-word Unicode case-insensitive matching and bounded Russian forms; list parts to match them alone |
|
|
170
231
|
| `publicEntities` | `[]`; exact PERSON/ORGANIZATION/EDUCATION/LOCATION/EMAIL exception with HTTPS evidence and review date |
|
|
171
232
|
| `findings` | `[]`; validated detections for the original input, supplied by the caller |
|
|
@@ -192,7 +253,7 @@ new document recognizers; `documentEntities: false` turns them off explicitly.
|
|
|
192
253
|
| `strict` | `false`; aggressive stored-log personal attributes and bare values |
|
|
193
254
|
| `structuralFields` | `[]`; declared property names suppress weak name/ID/entropy heuristics in their string values |
|
|
194
255
|
|
|
195
|
-
Unknown options and invalid limits are errors. `knownSecretsFromEnv(env, names?)` discovers credential-named variables (excluding
|
|
256
|
+
Unknown options and invalid limits are errors. `knownSecretsFromEnv(env, names?, {minLength?})` discovers credential-named variables (excluding configuration names) when names are omitted, or uses an explicit selection, deduplicating nonempty values (or values of at least `minLength` characters). It never invokes shell commands or reads auth files. `personalVariants(entries)` generates numeric and ISO/day-first date spellings; name case variants are already handled by detection. Neither helper invents transliterations.
|
|
196
257
|
|
|
197
258
|
## Required detectors
|
|
198
259
|
|
|
@@ -242,6 +303,7 @@ Without a profile, full redaction is the library default. `transformation` selec
|
|
|
242
303
|
| `fake` | Keyed realistic names, valid dates/MRZ/checksummed numbers and reserved contact destinations |
|
|
243
304
|
| `date-shift` | Personal ISO dates; finite integer `days`, UTC calendar arithmetic; invalid dates become opaque |
|
|
244
305
|
| `bucket` | Personal safe integers; positive integer `size`, inclusive numeric interval |
|
|
306
|
+
| `encrypt` | Personal data only; deterministic AES-256-GCM token reversible with `decrypt` and the key (see below) |
|
|
245
307
|
|
|
246
308
|
All keyed modes require a key of at least 16 UTF-8 bytes. `format-preserving` is a non-reversible shape pseudonym, not NIST FF1 encryption; it does not preserve checksum validity, country-specific alphabets or guarantee uniqueness. Partial masks, date shifts and buckets intentionally retain information. Keep pseudonym keys outside logs. Residual verification checks the corresponding fully redacted representation; opted-in disclosures can remain recognizable in the transformed output. Use default redaction for publication that requires opaque output.
|
|
247
309
|
|
|
@@ -255,7 +317,7 @@ Generated replacements carry `faked: true`. A private bounded registry recognize
|
|
|
255
317
|
|
|
256
318
|
Faking is pseudonymisation: a private key and retained ages, relations and structure can permit linking. Keep the key out of published data. See [the realistic fake example](../examples/realistic-fakes.mjs) and [the complete requirement/research record](case-studies/issue-24/REQUIREMENTS.md).
|
|
257
319
|
|
|
258
|
-
`preserveEncoding: true` sanitizes decoded payloads with the same required engine union, re-encodes them and verifies decoding reproduces the sanitized content. Supported formats include base64/base64url, hex, percent, numeric HTML and UTF-8 byte escapes. Escaped JSON string content is sanitized and re-encoded even under default redaction so nested session JSON stays parseable.
|
|
320
|
+
`preserveEncoding: true` sanitizes decoded payloads with the same required engine union, re-encodes them and verifies decoding reproduces the sanitized content. Supported formats include base64/base64url, hex, percent, numeric HTML and UTF-8 byte escapes. Escaped JSON string content is sanitized and re-encoded even under default redaction so nested session JSON stays parseable. Wrapped base64 and hex keep their line width and line separator (`\n` or `\r\n`), and a wrapped run is reported as one finding. When the sanitized payload cannot be re-encoded in a form the decoder recognizes (for example hex shorter than 32 digits), the encoded text is masked as it stands rather than blocking the input. Known encoding candidates and nesting are bounded.
|
|
259
321
|
|
|
260
322
|
## Identity policy and structured sessions
|
|
261
323
|
|
|
@@ -303,9 +365,15 @@ status identifiers. Explicit `knownPersonal` values override these heuristics.
|
|
|
303
365
|
|
|
304
366
|
Oversized encoded runs receive a conservative complete-run `ENCODED_LIMIT`
|
|
305
367
|
redaction rather than aborting the record. Decoding probes are capped at 4,096
|
|
306
|
-
characters; full decoding stays capped at 8,192 characters.
|
|
307
|
-
|
|
308
|
-
|
|
368
|
+
characters; full decoding stays capped at 8,192 characters. A multi-line run is
|
|
369
|
+
treated as one wrapped payload only when its probe decodes as text or its lines
|
|
370
|
+
share one base64 width (a multiple of four, standard alphabet, not plain hex);
|
|
371
|
+
lists of identifiers or commit hashes are scanned line by line. Up to 4,096
|
|
372
|
+
textually decodable candidates are scanned per 64 KiB window of the input;
|
|
373
|
+
further candidates in a denser window receive `ENCODED_LIMIT` redactions rather
|
|
374
|
+
than an `ERR_LIMIT` for the whole text. Whole-input, record and worker limits
|
|
375
|
+
still apply. This fallback can remove large non-sensitive encoded outputs; its
|
|
376
|
+
finding makes that decision auditable.
|
|
309
377
|
|
|
310
378
|
## Streaming and worker publication
|
|
311
379
|
|
|
@@ -368,7 +436,7 @@ The optional ESLint plugin exports `require-sanitized-output` from the package's
|
|
|
368
436
|
|
|
369
437
|
## Native documented entity catalogs
|
|
370
438
|
|
|
371
|
-
`entityCatalogs` exposes source URLs, source-page SHA256 and documented names for Google (
|
|
439
|
+
`entityCatalogs` exposes source URLs, source-page SHA256 and documented names for Google (214), Azure (180) and AWS (36). Their canonical uppercase underscore labels are recognized natively, with complete value spans, independent of cloud services. The package also implements selected automatic formats and checksum algorithms. Catalog vocabulary support does not reproduce proprietary NER, image/document inspection, regional identity validators, cloud risk analysis or their accuracy. See the coverage table for that distinction.
|
|
372
440
|
|
|
373
441
|
## Two-pass session publication
|
|
374
442
|
|
|
@@ -446,12 +514,28 @@ findings retain the normal engine union, confidence rules and verification.
|
|
|
446
514
|
This keeps EMAIL/account-ID values without per-value `publicEntities` entries.
|
|
447
515
|
The default policy continues to protect personal data.
|
|
448
516
|
|
|
449
|
-
`knownSecretsFromEnv(env = process.env, names?)` discovers
|
|
450
|
-
when `names` is omitted
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
`
|
|
454
|
-
|
|
517
|
+
`knownSecretsFromEnv(env = process.env, names?, { minLength = 1 }?)` discovers
|
|
518
|
+
nonempty exact values when `names` is omitted. A name is selected when it has a
|
|
519
|
+
credential keyword between `_` boundaries
|
|
520
|
+
(`/(?:^|_)(?:TOKEN|(?:API|ACCESS|PRIVATE)?KEY|SECRET|PASSWORD|PASSWD|CREDENTIALS?|O?AUTH|PAT|COOKIE)(?:_|$)/i`)
|
|
521
|
+
or ends in `PASSWORD`, `PASSWD`, `TOKEN` or `COOKIE` without one. This covers
|
|
522
|
+
`MY_SERVICE_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, `CODEX_API_KEY`,
|
|
523
|
+
`OPENCODE_API_KEY`, `DASHSCOPE_API_KEY`, `HF_TOKEN`, `TELEGRAM_BOT_TOKEN`,
|
|
524
|
+
`PGPASSWORD`, `GITHUBTOKEN`, `OAUTH` and `SESSION_COOKIE`.
|
|
525
|
+
Automatic discovery excludes names whose last segment describes configuration
|
|
526
|
+
rather than a credential: `_TRANSPORT`, `_MODE`, `_METHOD`, `_TYPE`, `_URL`,
|
|
527
|
+
`_URI`, `_HOST`, `_HOSTNAME`, `_PATH`, `_ENABLED`, `_DISABLED`, `_PORT`,
|
|
528
|
+
`_PROTOCOL`, `_SCHEME`, `_GUARD`, `_COUNT`, `_LIMIT`, `_FILE`, `_FILENAME`,
|
|
529
|
+
`_DIR`, `_DIRECTORY`, `_NAME` and `_ID` (for example `HIVE_MIND_GIT_AUTH_TRANSPORT`,
|
|
530
|
+
`HIVE_MIND_ROUTER_AUTH_GUARD`, `MAX_TOKEN_COUNT`, `SSH_KEY_PATH`, `SECRET_NAME`
|
|
531
|
+
and `AWS_ACCESS_KEY_ID`), plus `GOOGLE_APPLICATION_CREDENTIALS` (a file path) and
|
|
532
|
+
`GIT_CONFIG_KEY_<n>` (the name of a git setting). `GIT_CONFIG_VALUE_<n>` is
|
|
533
|
+
selected when its `GIT_CONFIG_KEY_<n>` names a header, password, token, secret,
|
|
534
|
+
cookie file or auth setting, such as `http.extraheader`. Credential names such as
|
|
535
|
+
`AUTH_TOKEN` and `AUTH_MODE_TOKEN` still contribute their exact values, including
|
|
536
|
+
short/low-entropy values; no value-shape filter can reliably distinguish passwords
|
|
537
|
+
from configuration. Pass `minLength` to skip shorter values. An explicit array
|
|
538
|
+
bypasses the name exclusions; `[]` selects nothing.
|
|
455
539
|
These values are loaded only when the helper or CLI `--env-secrets` is requested.
|
|
456
540
|
|
|
457
541
|
`await knownSecretsFromGitHubConfig({path?})` reads `oauth_token` and `api_token`
|
|
@@ -462,6 +546,9 @@ symlinks, aliases, files above 1 MiB and read failures produce value-free
|
|
|
462
546
|
`ERR_AUTH`. YAML quoting is parsed with the `yaml` package, with alias expansion
|
|
463
547
|
disabled. CLI `--gh-config` adds these exact values to `knownSecrets`.
|
|
464
548
|
`knownSecretsFromGitHubAuth` / `--gh-auth` remain available independently.
|
|
549
|
+
A `hosts.yml` that uses YAML aliases or is a symlink stays a hard `ERR_AUTH`
|
|
550
|
+
failure, so a caller relying on `--gh-config` never runs with a silently
|
|
551
|
+
incomplete list.
|
|
465
552
|
|
|
466
553
|
Plain log streams and CLI file/stdin/output/in-place paths preserve invalid
|
|
467
554
|
UTF-8 bytes exactly. Valid Unicode keeps its normal meaning for detection.
|
|
@@ -554,9 +641,33 @@ also applies independently within serialized JSON strings, to the existing
|
|
|
554
641
|
bounded decoding depth. Findings do not seed document-wide name propagation.
|
|
555
642
|
Repeated redaction is stable. Reusing a placeholder map for the same original
|
|
556
643
|
produces stable tokens and exact restoration; the existing placeholder-namespace
|
|
557
|
-
collision checks still reject input that already contains typed placeholders
|
|
644
|
+
collision checks still reject input that already contains typed placeholders
|
|
645
|
+
unless the transformation sets `existing: 'keep'`.
|
|
646
|
+
|
|
647
|
+
Capitalized words that identify nobody stay readable, so placeholder output keeps
|
|
648
|
+
its sentence: pronouns (`Вас`, `Я`), role and label cues (`Кандидат`, `Сотрудник`,
|
|
649
|
+
the default and configured `roles` and `personalLabels`) anywhere, and greetings,
|
|
650
|
+
function words and verbs (past tense `Звонила`, `Перенёс`, `Работал` before a
|
|
651
|
+
lowercase word; present `Работаю`, `Рекомендует`) at the start of a sentence:
|
|
558
652
|
|
|
559
|
-
|
|
653
|
+
```js
|
|
654
|
+
sanitize('Кандидат Петров Иван Сергеевич. Звонила Анна Смирнова', {
|
|
655
|
+
properNames: 'capitalized',
|
|
656
|
+
transformation: { mode: 'placeholder', map: false },
|
|
657
|
+
}).text;
|
|
658
|
+
// Кандидат Person-1. Звонила Person-2
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
A word that is also a given-name form (`Вера`, `Любовь`) never counts as such a
|
|
662
|
+
word, and a bare masculine `-л` form before a capitalized word may be a surname
|
|
663
|
+
(`Шагал Марк`), so both are still replaced. A compact organization, location,
|
|
664
|
+
school or person span found by the document rules (`ООО «Ромашка»`, `в Казани`)
|
|
665
|
+
replaces the proper-name fragments inside it as one typed value
|
|
666
|
+
(`Organization-1`, `Location-1`); spans with tabs or line breaks, or longer than
|
|
667
|
+
160 characters, keep the separate fragments. A bare oblique surname (`Иванову`)
|
|
668
|
+
links to the one person whose name contains it.
|
|
669
|
+
|
|
670
|
+
Other capitalized ordinary words still disappear; this is a structural heuristic.
|
|
560
671
|
Lowercase organizations without employment context stay; the document rules
|
|
561
672
|
replace a lowercase employer after `работал в` / `работала в`. Complete contact
|
|
562
673
|
and identifier findings take priority over overlapping proper-name fragments.
|
|
@@ -641,6 +752,13 @@ cues. Inputs allow at most 256 nonempty strings of 128 code units per list, with
|
|
|
641
752
|
no line breaks/NUL. Unknown lists and malformed replacement records fail with
|
|
642
753
|
`ERR_CONFIG`. Compilation uses a bounded 64-configuration cache.
|
|
643
754
|
|
|
755
|
+
`personalAttributes: ['вепс', 'старовер']` adds special-category stems to the
|
|
756
|
+
`strict` rule (it has no effect without `strict: true`). Each word matches as a
|
|
757
|
+
stem followed by up to eight letters, so `вепска` and `староверами` match, and the
|
|
758
|
+
words of a multi-word entry may be separated by spaces or tabs. Matches are
|
|
759
|
+
reported as `PERSONAL_ATTRIBUTE` with the `strict-attribute` rule. The default
|
|
760
|
+
list is empty.
|
|
761
|
+
|
|
644
762
|
## Callback transformations and stages
|
|
645
763
|
|
|
646
764
|
```js
|
|
@@ -742,3 +860,224 @@ one or both languages. Each invocation is limited to 1 Mi UTF-16 units; larger
|
|
|
742
860
|
inputs throw `ERR_LIMIT`. `debug` is off by default and emits counts only. Core
|
|
743
861
|
imports never load the optional entry or NLP.js; leaving it off adds no optional
|
|
744
862
|
runtime/module cost.
|
|
863
|
+
|
|
864
|
+
## Allow lists, suppression comments and template values
|
|
865
|
+
|
|
866
|
+
`allowList` keeps detected values that the caller has reviewed as safe, as in
|
|
867
|
+
Presidio's `allow_list`, Google's exclusion rules with
|
|
868
|
+
`MATCHING_TYPE_FULL_MATCH` and DataFog's `allowlist_patterns`. A string must
|
|
869
|
+
equal the whole detected value; a `RegExp` must match the whole value (it is
|
|
870
|
+
anchored internally, `g`/`y` flags are ignored). A partial match never shortens
|
|
871
|
+
a finding. At most 10 000 entries; anything else is `ERR_CONFIG`.
|
|
872
|
+
|
|
873
|
+
```js
|
|
874
|
+
sanitize('Contact support@example.org or alice@private.test', {
|
|
875
|
+
allowList: ['support@example.org', /.+@example\.org/],
|
|
876
|
+
});
|
|
877
|
+
// → 'Contact support@example.org or [REDACTED]'
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
`inlineAllow: true` honours the suppression comments of other scanners on the
|
|
881
|
+
same line: `gitleaks:allow`, `betterleaks:allow`, `kingfisher:allow`/`ignore`,
|
|
882
|
+
`trufflehog:ignore`, `sanitizer:allow`, detect-secrets/ripsecrets
|
|
883
|
+
`pragma: allowlist secret` and SonarQube `NOSONAR`. A line containing
|
|
884
|
+
`pragma: allowlist nextline secret` suppresses findings on the next line. It is
|
|
885
|
+
off by default because anyone who can write to a log could otherwise disable
|
|
886
|
+
redaction.
|
|
887
|
+
|
|
888
|
+
Both controls follow the same safety rules:
|
|
889
|
+
|
|
890
|
+
- declared `knownPersonal`/`knownSecrets` values, confirmed personal values and
|
|
891
|
+
structured `JSON_VALUE` denials are never exempted;
|
|
892
|
+
- credentials are exempted only together with `allowCredentialDisable: true`;
|
|
893
|
+
- they apply to text, structured and asynchronous paths alike.
|
|
894
|
+
|
|
895
|
+
Built-in credential rules also skip values that consist only of a template or
|
|
896
|
+
mask: `${PASSWORD}`, `$${PYPI_PASSWORD}`, `$DB_PASS`, `$1`, `<password>`,
|
|
897
|
+
`<Openverse API token>`, `[PASSWORD]`, `****`, `XXXXXXXX`, `...`, optionally
|
|
898
|
+
after `Bearer`/`Basic`/`token` and on either side of a `user:password` colon.
|
|
899
|
+
A mixed value such as `alice:${DB_PASSWORD}` or `<x>abc123…` is still redacted.
|
|
900
|
+
Findings from custom detectors are never dropped this way. The value-free debug
|
|
901
|
+
event `{event: 'template-value', rule}` reports each skipped match.
|
|
902
|
+
|
|
903
|
+
### Exempting findings with a hook
|
|
904
|
+
|
|
905
|
+
`exempt(finding, text) => boolean` keeps findings that the caller's own policy
|
|
906
|
+
knows are not sensitive: a configuration flag, a token counter, a version
|
|
907
|
+
range, a value another tool already masked. It runs after detection and before
|
|
908
|
+
transformation, once per finding. The finding carries `value` and the offsets
|
|
909
|
+
into `text`. Return `true` to keep the span unchanged and `false` to transform
|
|
910
|
+
it. Any other result, including `undefined` from a forgotten `return`, is
|
|
911
|
+
`ERR_CONFIG`.
|
|
912
|
+
|
|
913
|
+
```js
|
|
914
|
+
createSyncSanitizer({
|
|
915
|
+
credentialsOnly: true,
|
|
916
|
+
// The key before the value decides; offsets point into `text`.
|
|
917
|
+
exempt: (finding, text) =>
|
|
918
|
+
/FEATURE_TOKEN=$/.test(text.slice(0, finding.start)),
|
|
919
|
+
}).sanitize(
|
|
920
|
+
'FEATURE_TOKEN=keep-this-value-123 other_token=keep-this-value-123'
|
|
921
|
+
);
|
|
922
|
+
// → 'FEATURE_TOKEN=keep-this-value-123 other_token=[REDACTED]'
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
Unlike a callback transformation that returns the original value, which fails
|
|
926
|
+
with `ERR_RESIDUAL`, an exempted span is excluded from residual verification
|
|
927
|
+
by span, not by value. Verification of the published text calls the hook again
|
|
928
|
+
for every finding in the output, so the same value elsewhere is still caught
|
|
929
|
+
unless the hook keeps that span too. The hook is explicit code, so it can keep
|
|
930
|
+
credential findings without `allowCredentialDisable`. Declared
|
|
931
|
+
`knownSecrets`/`knownPersonal` values, confirmed personal values and structured
|
|
932
|
+
`JSON_VALUE` denials are never offered to it.
|
|
933
|
+
|
|
934
|
+
`createSanitizer` awaits a hook that returns a `Promise`.
|
|
935
|
+
`createSyncSanitizer` and `sanitize()` need a synchronous hook. A `Promise`
|
|
936
|
+
there is `ERR_CONFIG`.
|
|
937
|
+
|
|
938
|
+
## Per-type thresholds
|
|
939
|
+
|
|
940
|
+
`minConfidenceByType` maps finding types to a threshold that overrides
|
|
941
|
+
`minConfidence` for those types only, as in Presidio score thresholds or
|
|
942
|
+
Google's per-infoType likelihood:
|
|
943
|
+
|
|
944
|
+
```js
|
|
945
|
+
sanitize(text, { minConfidence: 0.5, minConfidenceByType: { PERSON: 0.8 } });
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Values must be numbers from 0 to 1. Credentials are never filtered by a
|
|
949
|
+
threshold.
|
|
950
|
+
|
|
951
|
+
## Character masks
|
|
952
|
+
|
|
953
|
+
The `mask` transformation accepts Presidio/Google-style tuning in addition to
|
|
954
|
+
`keepStart`, `keepEnd`, `minLength` and `marker`:
|
|
955
|
+
|
|
956
|
+
| Option | Meaning |
|
|
957
|
+
| --------------- | -------------------------------------------------------------- |
|
|
958
|
+
| `char` | One masking character (default `*`) |
|
|
959
|
+
| `ignore` | Characters that are never masked, e.g. `'-@.'` |
|
|
960
|
+
| `ignoreClasses` | Any of `digits`, `upper`, `lower`, `punctuation`, `whitespace` |
|
|
961
|
+
| `count` | Mask at most this many characters |
|
|
962
|
+
| `fromEnd` | Mask the last `count` characters instead of the first |
|
|
963
|
+
|
|
964
|
+
```js
|
|
965
|
+
sanitize('Card 4111-1111-1111-1111', {
|
|
966
|
+
transformation: { mode: 'mask', char: '#', ignore: '-', count: 12 },
|
|
967
|
+
});
|
|
968
|
+
// → 'Card ####-####-####-1111'
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
`marker` cannot be combined with the tuning options. A value with nothing left
|
|
972
|
+
to mask is fully redacted. Masks apply to personal data only.
|
|
973
|
+
|
|
974
|
+
## Deterministic encryption
|
|
975
|
+
|
|
976
|
+
`{mode: 'encrypt', key}` replaces each personal value with
|
|
977
|
+
`[ENCRYPTED:TYPE:…]`, comparable to Google's `CryptoDeterministicConfig` and
|
|
978
|
+
Presidio's `encrypt` operator. The token is AES-256-GCM with a synthetic IV
|
|
979
|
+
(HMAC of type and value), so equal values of one type produce equal tokens and
|
|
980
|
+
joins or counts still work. Keys are at least 16 UTF-8 bytes and are expanded
|
|
981
|
+
with HKDF-SHA256.
|
|
982
|
+
|
|
983
|
+
```js
|
|
984
|
+
import { sanitize, decrypt } from '@link-foundation/sensitive-data-sanitizer';
|
|
985
|
+
const key = process.env.SANITIZER_KEY;
|
|
986
|
+
const { text } = sanitize('Mail alice@private.test', {
|
|
987
|
+
transformation: { mode: 'encrypt', key },
|
|
988
|
+
});
|
|
989
|
+
decrypt(text, key); // → 'Mail alice@private.test'
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
`decrypt(text, key, {json: true})` escapes restored values inside JSON string
|
|
993
|
+
literals. A token that fails authentication throws `ERR_MAP`; a non-string text
|
|
994
|
+
throws `ERR_INPUT`. Credentials are still fully redacted and never encrypted.
|
|
995
|
+
Anyone holding the key can recover the values, so treat encrypted output as
|
|
996
|
+
pseudonymised, not anonymous.
|
|
997
|
+
|
|
998
|
+
## Declarative pattern detectors
|
|
999
|
+
|
|
1000
|
+
`createPatternDetector(specs, {id})` builds a synchronous Detector from
|
|
1001
|
+
Presidio-style pattern recognizers or Google custom infoTypes without writing
|
|
1002
|
+
code:
|
|
1003
|
+
|
|
1004
|
+
```js
|
|
1005
|
+
import {
|
|
1006
|
+
createPatternDetector,
|
|
1007
|
+
sanitize,
|
|
1008
|
+
} from '@link-foundation/sensitive-data-sanitizer';
|
|
1009
|
+
const detector = createPatternDetector([
|
|
1010
|
+
{
|
|
1011
|
+
type: 'MEDICAL_RECORD',
|
|
1012
|
+
category: 'personal',
|
|
1013
|
+
patterns: [{ regex: /\bMRN-\d{6}\b/g, confidence: 0.4 }],
|
|
1014
|
+
denyList: ['Mr. Bean'],
|
|
1015
|
+
context: ['mrn', 'medical record'],
|
|
1016
|
+
contextWindow: { before: 64, after: 16 },
|
|
1017
|
+
contextBoost: 0.35,
|
|
1018
|
+
validate: (value) => value !== 'MRN-000000',
|
|
1019
|
+
},
|
|
1020
|
+
]);
|
|
1021
|
+
sanitize('medical record MRN-123456', { detectors: [detector] });
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
| Field | Meaning |
|
|
1025
|
+
| ------------------ | ---------------------------------------------------------------------------------------------- |
|
|
1026
|
+
| `type`, `category` | Uppercase type; `personal` or `credential` |
|
|
1027
|
+
| `rule` | Lowercase rule id (default `custom-pattern`) |
|
|
1028
|
+
| `confidence` | Base confidence (default 0.6) |
|
|
1029
|
+
| `patterns` | Up to 64 `RegExp`, strings or `{regex, confidence, group}` (a capture group narrows the span) |
|
|
1030
|
+
| `denyList` | Whole-word values, case-insensitive unless `caseSensitive: true`; `denyListConfidence` (1) |
|
|
1031
|
+
| `context` | Hotwords (strings or RegExp) searched in `contextWindow` (number or `{before, after}`, ≤ 4096) |
|
|
1032
|
+
| `contextBoost` | Added when a hotword is present (default 0.35, floor 0.4); a negative value lowers confidence |
|
|
1033
|
+
| `validate` | `false` drops a match; a number sets its confidence |
|
|
1034
|
+
|
|
1035
|
+
Malformed specifications throw `ERR_CONFIG`.
|
|
1036
|
+
|
|
1037
|
+
`createIdentifierDetector(rules, {id})` adapts rules with the shape of the
|
|
1038
|
+
exported `nationalRules` table (`{type, pattern, context, checksum, plausible?,
|
|
1039
|
+
valid?}`) so callers can add or replace national identifiers. `identifierChecks`
|
|
1040
|
+
exposes the checksum and format validators (Luhn, mod-11, mod-97, Verhoeff and
|
|
1041
|
+
the national algorithms) for reuse; a valid checksum never proves issuance.
|
|
1042
|
+
|
|
1043
|
+
## Offline token checksums
|
|
1044
|
+
|
|
1045
|
+
GitHub (`ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_`) and npm (`npm_`) tokens end in
|
|
1046
|
+
six base62 characters holding the CRC32 of the 30 characters after the first
|
|
1047
|
+
`_`. As in Kingfisher and osv-scalibr, the native `github`/`npm` rules and the
|
|
1048
|
+
vendored Gitleaks/Betterleaks rules give a checksum-valid token confidence
|
|
1049
|
+
0.99. A token whose checksum does not match keeps its ordinary confidence and
|
|
1050
|
+
is still redacted, because truncated or retyped tokens remain sensitive. No
|
|
1051
|
+
network request is made. The exported `tokenChecksum(value)` returns `true`,
|
|
1052
|
+
`false` or `undefined` (not a checksummed format), and
|
|
1053
|
+
`tokenWithChecksum(prefix, body)` builds checksum-valid test fixtures at
|
|
1054
|
+
runtime so repositories need not store token literals.
|
|
1055
|
+
|
|
1056
|
+
## SARIF reports
|
|
1057
|
+
|
|
1058
|
+
`toSarif(report, {version, path})` converts a CLI `scan` report, an array of
|
|
1059
|
+
`{path, findings}` or a findings array into SARIF 2.1.0 for GitHub code
|
|
1060
|
+
scanning and other CI viewers. Rules are keyed by `finding.rule`; credential
|
|
1061
|
+
findings are `error` and personal findings `warning`. Regions carry
|
|
1062
|
+
`charOffset`, `charLength`, `startLine` and `startColumn` in UTF-16 code units.
|
|
1063
|
+
Skipped files become tool execution notifications. Values are never included.
|
|
1064
|
+
|
|
1065
|
+
```sh
|
|
1066
|
+
sensitive-data-sanitizer scan --format sarif src > results.sarif
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
`--format` accepts `json` (default) or `sarif` and is only valid for `scan`.
|
|
1070
|
+
|
|
1071
|
+
CLI `scan` accepts several paths, so a CI step or a
|
|
1072
|
+
[pre-commit](https://pre-commit.com) hook can pass the staged files directly.
|
|
1073
|
+
The repository ships `.pre-commit-hooks.yaml` with two hooks:
|
|
1074
|
+
`sensitive-data-sanitizer` (credentials only) and
|
|
1075
|
+
`sensitive-data-sanitizer-pii` (credentials and personal data).
|
|
1076
|
+
|
|
1077
|
+
```yaml
|
|
1078
|
+
repos:
|
|
1079
|
+
- repo: https://github.com/link-foundation/sensitive-data-sanitizer
|
|
1080
|
+
rev: v0.20.0
|
|
1081
|
+
hooks:
|
|
1082
|
+
- id: sensitive-data-sanitizer
|
|
1083
|
+
```
|
package/docs/COVERAGE.md
CHANGED
|
@@ -6,9 +6,9 @@ Coverage is a union of maintained detectors, explicit known values, context and
|
|
|
6
6
|
|
|
7
7
|
The native provider supplements in `src/rules.js` cover GitHub, GitLab, OpenAI, Anthropic, AWS access IDs, Google API/OAuth, Slack, Stripe, SendGrid, Twilio, npm, PyPI, Telegram, Discord, Hugging Face, Shopify, Databricks, Square, Mailchimp, DigitalOcean, Vault, Grafana, Linear, Notion, Vercel, and JWT-shaped values. Prefix matches also protect selected truncated tokens; they do not validate a token with a provider.
|
|
8
8
|
|
|
9
|
-
The default native engine bundles 221 text rules from Gitleaks 8.30.1 (222 upstream rules; the path-only PKCS12 rule is outside text detection), compiled unchanged with RE2/WASM or RE2JS for inputs above
|
|
9
|
+
The default native engine bundles 221 text rules from Gitleaks 8.30.1 (222 upstream rules; the path-only PKCS12 rule is outside text detection), compiled unchanged with RE2/WASM or RE2JS for inputs above 1 MiB (re2-wasm has a fixed 16 MiB heap shared by every compiled rule and copied subject). Dense rule matches switch to RE2JS after at most eight WASM matches per view, bounding repeated whole-input copies without truncating secrets or changing anchors. The MIT license, commit and sync script are retained in `src/vendor/gitleaks`. Because Gitleaks is no longer updated, 203 further rules from its maintained successor Betterleaks 1.10.0 (ids absent from the Gitleaks set, such as Groq, Cerebras, xAI, Vercel, Supabase and Cloudflare v2 tokens) run the same way as `betterleaks/<id>` findings, with their MIT license, provenance and `scripts/sync-betterleaks.py` in `src/vendor/betterleaks`. Their entropy floors and secret-only `matchesAny`/`containsAny` filters are honoured; BPE token-ratio and nearby-line filters, live validation, validation-only components and the context-dependent `generic-*` rules are not, so these rules can only report more than Betterleaks, not less. Keyword prefilters and configured entropy thresholds run natively; upstream comment/path allowlists cannot suppress a sanitizer finding. Quoted hex/base64 entropy detection supplements those rules. The default asynchronous engine additionally uses the pinned Secretlint recommended scanners, excluding its comment-filter rule. Embedded disable directives in untrusted text cannot suppress findings. Optional Gitleaks and TruffleHog invoke their installed detector sets, and custom detectors can add formats. Service counts are not a measure of comparative recall.
|
|
10
10
|
|
|
11
|
-
Context rules recognize credential labels with bounded key affixes, JSON and nested escaped JSON, YAML/env/INI assignments, shell flags, XML values, URL userinfo, authorization headers, cookies, webhook paths, and complete or truncated private-key PEM blocks. Low-entropy contextual values such as `password=1234` are protected. Generic token counters, booleans/null/language literals and ordinary Git hashes are excluded to retain useful logs. Supply literal known values for ambiguous credentials or unusual key names.
|
|
11
|
+
Context rules recognize credential labels with bounded key affixes, JSON and nested escaped JSON, YAML/env/INI assignments, Ruby/Perl/PHP hash rockets before a quoted literal (`'password' => '…'`), shell flags including plural credential names (`--access-tokens`, but not `--max-tokens`), XML values (also with attributes and camelCase element names), values that start with markup (`token: <script>…`; a bare `<placeholder>` stays), all-caps CI/test credential markers that contain a digit (`SYNTHETIC_TOKEN_ALPHA_123456`, rule `marker-token`), URL userinfo, authorization headers, cookies, webhook paths, and complete or truncated private-key PEM blocks. Low-entropy contextual values such as `password=1234` are protected; a number or boolean under a configuration name (`AUTH_MODE=2`, `ROUTER_AUTH_GUARD=0`) is a setting. Generic token counters, booleans/null/language literals and ordinary Git hashes are excluded to retain useful logs. Supply literal known values for ambiguous credentials or unusual key names.
|
|
12
12
|
|
|
13
13
|
Broader unquoted entropy scanning is opt-in (`paranoid: true`), uses Shannon entropy ≥4.2 on bounded candidates, and exempts ordinary hex hashes and common tool identifiers. Quoted hex/base64 and entropy checks belonging to bundled provider rules run by default. Entropy has both false positives and false negatives; contextual detection and known-secret dictionaries remain necessary.
|
|
14
14
|
|
|
@@ -185,7 +185,7 @@ records standards, alternatives and regression coverage for each feature.
|
|
|
185
185
|
| Local subprocess | Default 30 seconds and 10 MiB report/stdout cap; failures block output |
|
|
186
186
|
| Git objects | 10,000 objects, 10 MiB per object and 100 MiB total by default |
|
|
187
187
|
|
|
188
|
-
Supported decoding includes canonical base64/base64url, line-wrapped base64, hex, percent, HTML entities, UTF-8 byte escapes and JSON string escapes, with fatal UTF-8 checks for decoded payloads. Plain log files and streams preserve invalid UTF-8 bytes reversibly while scanning surrounding valid text; structured JSON/JSONL and configuration remain strict. Opt-in encoding preservation round-trips sanitized payloads; nested escaped JSON retains its structure by default. Arbitrarily split fragments, unlimited nesting, compressed/encrypted payloads, malformed encodings, archives, PDFs and OCR/images are outside this text profile. Whole-input/candidate limits fail; oversized textual percent/base64 runs receive audited complete-run redaction. Unsupported representations may remain undetected. `decode: false` intentionally reduces coverage. A caller needing another format should decode it through a bounded preprocessing adapter and retain correct original spans.
|
|
188
|
+
Supported decoding includes canonical base64/base64url, line-wrapped base64, hex, percent, HTML entities, UTF-8 byte escapes and JSON string escapes, with fatal UTF-8 checks for decoded payloads. The detection view additionally decodes `\uXXXX`, `\u{X}`, `\x{X}`, `\UXXXXXXXX` and `%uXXXX` escapes, runs of four or more `U+XXXX` code points, CSS escape runs (`\74 \6f \6b`), drops NUL bytes of UTF-16 text read as single bytes, zero-width and bidirectional format characters and ANSI colour sequences, removes inline HTML formatting tags (`<em>`, `<span class=…>`) and turns block tags into line breaks, while findings keep their original offsets. A finding inside a `U+` or CSS escape run redacts the whole run, so no undecodable fragment of it remains. The CLI `redact` and `scan` commands also read UTF-16LE/BE files that start with a byte order mark and write redacted output back in the same encoding; BOM-less UTF-16 and stream input must be UTF-8. Plain log files and streams preserve invalid UTF-8 bytes reversibly while scanning surrounding valid text; structured JSON/JSONL and configuration remain strict. Opt-in encoding preservation round-trips sanitized payloads; nested escaped JSON retains its structure by default. Arbitrarily split fragments, unlimited nesting, compressed/encrypted payloads, malformed encodings, archives, PDFs and OCR/images are outside this text profile. Whole-input/candidate limits fail; oversized textual percent/base64 runs receive audited complete-run redaction. Unsupported representations may remain undetected. `decode: false` intentionally reduces coverage. A caller needing another format should decode it through a bounded preprocessing adapter and retain correct original spans.
|
|
189
189
|
|
|
190
190
|
Default text mode can replace JSON numeric values with string markers and change syntax/types. Opt-in structured JSON/JSONL mode scans string keys/values separately, preserves primitive types, and rejects malformed output or key collisions. Numeric personal values need a separate caller schema policy. Existing quoted JSON strings and escaped-string fixtures retain their delimiters. Review sanitized artifacts for their intended consumer.
|
|
191
191
|
|
|
@@ -193,7 +193,7 @@ An unchanged result is not proof that text contains no sensitive information. Th
|
|
|
193
193
|
|
|
194
194
|
## Entity catalogs and optional modes
|
|
195
195
|
|
|
196
|
-
Documented Google/Azure/AWS catalog names (
|
|
196
|
+
Documented Google/Azure/AWS catalog names (214/180/36) are recognized as explicit native labels, including canonical underscore and original camel-case forms. Generic DATE is excluded so ordinary headers/calendar dates remain intact; personal birth-date context still qualifies. This vocabulary is distinct from automatic vendor NLP/image/cloud-service features. Typed transformations and confidence are described in [API.md](API.md); full redaction remains the default. Public Wikidata names, known role/domain emails and public resolver/documentation IPs use explicit offline knowledge and heuristics. Opt-in network lookup does not establish identity; private overrides and credential priority remain authoritative.
|
|
197
197
|
|
|
198
198
|
Large text logs use bounded record streaming and worker file publication. Plain-text records inherit the configurable total limit (1 GiB by default), so memory grows with the largest held record. The whole-string default is 32 Mi UTF-16 units. `credentialsOnly: true` preserves personal/account details across all finding sources while retaining credential detection and verification. The review corpus benchmark is a finite, synthetic regression comparison; [method, settings and results](../experiments/benchmark/README.md) explain its scope. It does not demonstrate general market-wide superiority.
|
|
199
199
|
|
|
@@ -480,17 +480,27 @@ remove ordinary uses of unambiguous category words. Bounded Russian case/stem
|
|
|
480
480
|
rules cover disability, ethnicity, religion, diagnoses, intimate life and criminal
|
|
481
481
|
status, including `инвалидом`, `армянин`, `еврейка`, `казах`, `христианин`, `иудей`,
|
|
482
482
|
`эпилепсией`, `астма`, `онкологию`, `туберкулез`, `психиатра`, `депрессия`,
|
|
483
|
-
`гепатит`, `гей` and `отбывал
|
|
484
|
-
|
|
483
|
+
`гепатит`, `гей` and `отбывал наказание`, plus nationalities (`узбечка`,
|
|
484
|
+
`таджик`, `чеченец`, `грузинка`, `азербайджанец`, `цыганка`, `кореянка`),
|
|
485
|
+
religions (`протестант`), health (`инсульт`, `шизофрения`, `слабослышащий`,
|
|
486
|
+
`колясочник`, `передвигается на коляске`, `аутизм`), sexuality (`бисексуал`,
|
|
487
|
+
`трансгендер`), convictions (`сидел за кражу`) and trade-union membership
|
|
488
|
+
(`член профсоюза`). English coverage includes `epilepsy`, `gay`, `Armenian`,
|
|
489
|
+
`autism`, `transgender`, `wheelchair user`, `trade union member` and
|
|
490
|
+
`served time for`. Country, region and language words (`Узбекистан`, `Чувашия`,
|
|
491
|
+
`грузинский язык`) are not attributes. `vocabulary.personalAttributes` adds caller
|
|
492
|
+
stems. Explicit category cues such as `по национальности X`,
|
|
485
493
|
`исповедует X`, `болеет X`, `на учете у X`, `diagnosed with X` and `ethnicity: X`
|
|
486
494
|
protect one following word (including hyphenated words) up to 64 characters,
|
|
487
495
|
including unfamiliar values outside the lexicon. These cues stay on one line.
|
|
488
|
-
Ambiguous English `disabled`, `single`, `man`, `men`, `russian
|
|
489
|
-
|
|
496
|
+
Ambiguous English `disabled`, `single`, `man`, `men`, `russian`,
|
|
497
|
+
`asian`, `uzbek`, `korean`, `chinese`, `georgian`, `kazakh`, `roma` and
|
|
498
|
+
`union member` require an adjacent personal cue (pronoun/person role, personal
|
|
490
499
|
label, or following person role). Thus `he is disabled`, `candidate is single`
|
|
491
500
|
and `gender: male` redact, while `the module is disabled`, `single run finished`,
|
|
492
501
|
`single-flight lock acquired`, `man page missing`, `locale russian selected`
|
|
493
|
-
|
|
502
|
+
`asian region endpoint`, `Korean locale enabled` and `union member list`
|
|
503
|
+
remain intact. Bare operational JSON statuses
|
|
494
504
|
`disabled`, `single` and `Russian` also remain intact; explicit personal labels
|
|
495
505
|
and caller-declared known personal values still redact. A cue in another clause
|
|
496
506
|
or string value does not make an ambiguous word personal.
|