@link-foundation/sensitive-data-sanitizer 0.19.1 → 0.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/docs/COVERAGE.md +8 -0
- package/package.json +1 -1
- package/src/document-fields.js +18 -1
- package/src/document-person-cues.js +4 -1
- package/src/vocabulary.js +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 4afc88d: Read `Mr`, `Ms` and `Dr` as person cues only when capitalized, so lowercase `ms` (milliseconds) and `dr` before a capitalized log word stay. Treat compound JSON keys ending in a personal field, such as `recruiterDisplayName`, `authorFullName` and `resumePdfOriginalFileName`, as person fields.
|
|
8
|
+
|
|
3
9
|
## 0.19.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/docs/COVERAGE.md
CHANGED
|
@@ -370,6 +370,11 @@ this explicit vocabulary: `firstName`, `lastName`, `middleName`, `patronymic`,
|
|
|
370
370
|
sensitive in full, including ISO birth timestamps. Thus `first_name` and
|
|
371
371
|
`firstName`, or `birth_date` and `birthDate`, follow the same policy. These are
|
|
372
372
|
schema assumptions: `contact` or `company` can hold operational data too.
|
|
373
|
+
Person fields also match as a word-boundary suffix of a compound key, so
|
|
374
|
+
`recruiterDisplayName`, `author_full_name` and `resumePdfOriginalFileName` are
|
|
375
|
+
person fields. The same suffix rule applies to `vocabulary.personalFields`.
|
|
376
|
+
Other types match only the whole key (`maxAge` and `fileName` stay), and a
|
|
377
|
+
bare `name` is not in the list.
|
|
373
378
|
Existing native labels still apply. Unlisted URL-named fields have ordinary
|
|
374
379
|
content detection; arbitrary names do not imply sensitivity. Declare them with
|
|
375
380
|
[`sensitiveFields`](API.md#caller-declared-sensitive-fields) to replace complete
|
|
@@ -542,6 +547,9 @@ corpus, unbounded input, parallel builds or external analysis service.
|
|
|
542
547
|
|
|
543
548
|
Document rules recognize a capitalized Latin name after `candidate`, `recruiter`,
|
|
544
549
|
`applicant`, `Dear`, `Hi`, `Mr`/`Mr.`, `Ms`/`Ms.`, `Dr`/`Dr.`, or `message from`.
|
|
550
|
+
Honorifics count only when capitalized: lowercase `ms` (milliseconds), `mr` and
|
|
551
|
+
`dr` are not cues, and `Ms` right after a number is read as a unit, so
|
|
552
|
+
`took 30 ms Retry scheduled` and `see dr Drive mount` stay unchanged.
|
|
545
553
|
Only the name is replaced; punctuation and the cue remain. These rules work
|
|
546
554
|
without enabling Latin script matching. Finite generic heading nouns such as
|
|
547
555
|
`Stage` and `Status` are preserved: `Candidate Stage changed` and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-foundation/sensitive-data-sanitizer",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
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",
|
package/src/document-fields.js
CHANGED
|
@@ -25,7 +25,24 @@ const types = new Map(
|
|
|
25
25
|
)
|
|
26
26
|
);
|
|
27
27
|
export function documentFieldType(field) {
|
|
28
|
-
|
|
28
|
+
const type = types.get(normalizeFieldName(field));
|
|
29
|
+
if (type) {
|
|
30
|
+
return type;
|
|
31
|
+
}
|
|
32
|
+
// Compound person keys (`recruiterDisplayName`, `author_full_name`) end in
|
|
33
|
+
// a listed person field. Only PERSON entries extend this way, so generic
|
|
34
|
+
// suffixes such as `maxAge` or `homeSchool` keep their exact-match meaning,
|
|
35
|
+
// and a bare `name` is never listed.
|
|
36
|
+
return fieldSuffixes(field).some((suffix) => types.get(suffix) === 'PERSON')
|
|
37
|
+
? 'PERSON'
|
|
38
|
+
: undefined;
|
|
39
|
+
}
|
|
40
|
+
// Word-boundary suffixes of a key, normalized; the whole key comes first.
|
|
41
|
+
export function fieldSuffixes(field) {
|
|
42
|
+
const words = field
|
|
43
|
+
.split(/[\s_.-]+|(?<=[\p{Ll}\p{N}])(?=\p{Lu})|(?<=\p{Lu})(?=\p{Lu}\p{Ll})/u)
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
return words.map((_, i) => words.slice(i).join('').toLowerCase());
|
|
29
46
|
}
|
|
30
47
|
export const normalizeFieldName = (field) =>
|
|
31
48
|
field.replace(/[_-]/g, '').toLowerCase();
|
|
@@ -4,8 +4,11 @@ import { collectMatches } from './detection.js';
|
|
|
4
4
|
// capitalized-word matching for the rest of an English log or document.
|
|
5
5
|
const boundary = '[\\p{L}\\p{M}\\p{N}_]';
|
|
6
6
|
const name = "[A-Z][a-z]{1,47}(?:['’-][A-Z]?[a-z]{1,47})?";
|
|
7
|
+
// Honorifics count only when capitalized: lowercase `ms` is the millisecond
|
|
8
|
+
// unit in logs and lowercase `dr` is usually a word or path fragment. A
|
|
9
|
+
// capitalized `Ms` right after a number is still a unit (`took 30 Ms Retry`).
|
|
7
10
|
const cue =
|
|
8
|
-
'(?:[Cc]andidate|[Rr]ecruiter|[Aa]pplicant|[Dd]ear|[Hh]i|
|
|
11
|
+
'(?:[Cc]andidate|[Rr]ecruiter|[Aa]pplicant|[Dd]ear|[Hh]i|Mr\\.?|(?<!\\d[ \\t]*)Ms\\.?|Dr\\.?|[Mm]essage[ \\t]+from)';
|
|
9
12
|
const pattern = new RegExp(
|
|
10
13
|
`(?<!${boundary})${cue}[ \\t]+(${name})(?!${boundary})`,
|
|
11
14
|
'gu'
|
package/src/vocabulary.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { failure, collectMatches } from './detection.js';
|
|
2
2
|
import { wordPattern } from './personal-variants.js';
|
|
3
|
-
import { normalizeFieldName } from './document-fields.js';
|
|
3
|
+
import { fieldSuffixes, normalizeFieldName } from './document-fields.js';
|
|
4
4
|
|
|
5
5
|
export const defaultVocabulary = Object.freeze(
|
|
6
6
|
Object.fromEntries(
|
|
@@ -170,7 +170,8 @@ export function vocabularyField(field, options) {
|
|
|
170
170
|
if (!vocabulary) {
|
|
171
171
|
return undefined;
|
|
172
172
|
}
|
|
173
|
-
return vocabulary.fields.has(normalizeFieldName(field))
|
|
173
|
+
return vocabulary.fields.has(normalizeFieldName(field)) ||
|
|
174
|
+
fieldSuffixes(field).some((suffix) => vocabulary.fields.has(suffix))
|
|
174
175
|
? true
|
|
175
176
|
: options.vocabulary.personalFields?.replace
|
|
176
177
|
? false
|