euredact 0.3.7 → 0.3.9
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 +56 -2
- package/dist/cjs/rules/cues.js +240 -0
- package/dist/cjs/rules/engine.js +130 -26
- package/dist/cjs/rules/suppressors.js +57 -11
- package/dist/cjs/types.js +10 -0
- package/dist/esm/rules/cues.d.ts +121 -0
- package/dist/esm/rules/cues.js +235 -0
- package/dist/esm/rules/engine.d.ts +32 -0
- package/dist/esm/rules/engine.js +129 -26
- package/dist/esm/rules/suppressors.js +57 -11
- package/dist/esm/types.d.ts +10 -0
- package/dist/esm/types.js +10 -0
- package/package.json +1 -1
|
@@ -40,11 +40,16 @@ const MATH_AFTER = /^\s*[=+\-×÷*/]/;
|
|
|
40
40
|
const COUNTRY_PREFIX_HYPHEN = /(?:^|[^A-Za-z0-9])[A-Z]{1,2}-\s*$/;
|
|
41
41
|
const SEQUENTIAL_PATTERNS = /^(?:0{6,}|1234567890?|0123456789|9876543210?|1111111111?|000000000|123456789)$/;
|
|
42
42
|
// YEAR_PATTERN kept for reference but replaced by RECENT_YEAR in suppressYearAsPostal
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
43
|
+
// `ID_LABEL_BEFORE` and `suppressPhoneAfterIdLabel` lived here. Their labels are
|
|
44
|
+
// now typed entries in `cues.ts`, and a phone-shaped span behind one is
|
|
45
|
+
// *relabelled* rather than dropped.
|
|
46
|
+
//
|
|
47
|
+
// Dropping was the wrong verb. The span is found either way, so removing the
|
|
48
|
+
// claim decided only whether the value was masked — and the answer was "no":
|
|
49
|
+
// "Rijksregisternummer: 85.03.19-284.73" produced no detection at all, a
|
|
50
|
+
// redaction library printing in full an identifier it had recognised and
|
|
51
|
+
// rejected. The 653-miss word-boundary lesson recorded here moved with the
|
|
52
|
+
// labels; see the module comment in `cues.ts`.
|
|
48
53
|
const SERVICE_NUMBER = /^0800[\-\s]/;
|
|
49
54
|
const DATE_PATTERN_FULL = /^\d{2}[-/.]\d{2}[-/.]\d{4}$|^\d{4}[-/.]\d{2}[-/.]\d{2}$/;
|
|
50
55
|
const PASSPORT_CONTEXT_BEFORE = /(?:Reisepass|passport|passeport|paspoort|Bisheriger\s+Reisepass)\s*(?:Nr\.?|Nummer|nummer|number|n[°o])?\s*[:.]?\s*$/i;
|
|
@@ -338,11 +343,52 @@ function suppressYearAsPostal(text, match) {
|
|
|
338
343
|
// Suppress: recent years without postal context are almost never postal codes
|
|
339
344
|
return true;
|
|
340
345
|
}
|
|
341
|
-
|
|
342
|
-
|
|
346
|
+
// ── Postal code: disqualified by the word in front of it ───────────────
|
|
347
|
+
//
|
|
348
|
+
// The mirror image of `rules/cues.ts`. There a label promotes a type; here a
|
|
349
|
+
// word refuses one. Both were reported together: a bare four-digit run becomes
|
|
350
|
+
// a POSTAL_CODE as soon as *any* real postal code establishes the country,
|
|
351
|
+
// which is to say in almost every real document, because POSTAL_CONTEXT_NEAR
|
|
352
|
+
// above then rescues it from suppressYearAsPostal. Measured on the training
|
|
353
|
+
// corpus: 55 bare years in prose ("Opgericht in 2016", "Fondée en 2017") plus
|
|
354
|
+
// telephone extensions ("(toest. 3841)", "(poste interne 3318)", "(ext. 2219)").
|
|
355
|
+
/**
|
|
356
|
+
* Prepositions that can only be temporal. Nothing is ever located "since 2018",
|
|
357
|
+
* so these disqualify a postal code on their own.
|
|
358
|
+
*/
|
|
359
|
+
const YEAR_WORD_BEFORE = /(?<![A-Za-z0-9_])(?:sinds|since|depuis|seit|siden|sedan|desde|vuonna|anno|dal)\s+$/i;
|
|
360
|
+
/**
|
|
361
|
+
* "in", "en" and "im" are *both* temporal and locative, and in exactly the
|
|
362
|
+
* countries whose postal codes are year-shaped. Belgian 2000 is Antwerp and
|
|
363
|
+
* 2018 is one of its districts, so "Rustige ligging in 2018, vlakbij openbaar
|
|
364
|
+
* vervoer" is an address, not a date — an earlier version of this rule
|
|
365
|
+
* suppressed it and lost a real postal code, which is the worse error for a
|
|
366
|
+
* redaction tool. So an ambiguous preposition needs a founding or payment
|
|
367
|
+
* participle in front of it, which is what every case in the report had:
|
|
368
|
+
* "Opgericht in 2016", "Fondée en 2017", "versé en 2025".
|
|
369
|
+
*/
|
|
370
|
+
const YEAR_VERB_BEFORE = /(?<![A-Za-z0-9_])(?:opgericht|gesticht|opgestart|founded|established|created|fond[ée]e?|cr[ée][ée]e?|gegründet|gegruendet|errichtet|grundlagt|grundad|perustettu|fundada|fundado|fondata|costituita|vers[ée]|betaald|uitbetaald|ausgezahlt|paid|geboren|born)\s+(?:in|en|im|op|the)\s+$/i;
|
|
371
|
+
/**
|
|
372
|
+
* A capitalised word immediately after keeps the candidate: "in 2000 Antwerpen"
|
|
373
|
+
* really is a postal code, and so is the "AZ" of a Dutch "1105 AZ". Without
|
|
374
|
+
* this the rule would suppress the very addresses it is meant to leave alone.
|
|
375
|
+
*/
|
|
376
|
+
const TOWN_AFTER = /^\s*[A-ZÀ-ÞĀ-Ž]/;
|
|
377
|
+
/**
|
|
378
|
+
* A telephone extension marker. "poste interne 3318" puts one qualifier word
|
|
379
|
+
* between the marker and the number, so allow exactly one.
|
|
380
|
+
*/
|
|
381
|
+
const EXTENSION_BEFORE = /(?<![A-Za-z0-9_])(?:toest(?:el)?|ext|extension|poste|durchwahl|doorkiesnummer|tst|nebenstelle)\.?\s*(?:[a-zà-ÿ]{2,10}\s*)?[:.]?\s*$/i;
|
|
382
|
+
/** Suppress a postal code the word in front of it rules out. */
|
|
383
|
+
function suppressPostalAfterDisqualifyingWord(text, match) {
|
|
384
|
+
if (match.patternDef.entityType !== EntityType.POSTAL_CODE)
|
|
343
385
|
return false;
|
|
344
|
-
const
|
|
345
|
-
|
|
386
|
+
const before = text.slice(Math.max(0, match.start - 24), match.start);
|
|
387
|
+
if (EXTENSION_BEFORE.test(before))
|
|
388
|
+
return true;
|
|
389
|
+
return RECENT_YEAR.test(match.text.trim())
|
|
390
|
+
&& (YEAR_WORD_BEFORE.test(before) || YEAR_VERB_BEFORE.test(before))
|
|
391
|
+
&& !TOWN_AFTER.test(text.slice(match.end, match.end + 3));
|
|
346
392
|
}
|
|
347
393
|
function suppressPhoneServiceNumber(_text, match) {
|
|
348
394
|
if (match.patternDef.entityType !== EntityType.PHONE)
|
|
@@ -788,11 +834,11 @@ function suppressRequiresContext(text, match) {
|
|
|
788
834
|
return !match.patternDef.contextKeywords.some(kw => context.includes(kw.toLowerCase()));
|
|
789
835
|
}
|
|
790
836
|
const TYPE_SUPPRESSORS = {
|
|
791
|
-
[EntityType.PHONE]: [suppressCurrency, suppressUnits, suppressReference, suppressMath,
|
|
837
|
+
[EntityType.PHONE]: [suppressCurrency, suppressUnits, suppressReference, suppressMath, suppressPhoneServiceNumber, suppressPhoneDateOverlap, suppressPhoneAsNumberRange],
|
|
792
838
|
[EntityType.NATIONAL_ID]: [suppressCurrency, suppressUnits, suppressReference, suppressLegal, suppressMath, suppressNatidAsPassport, suppressSeNatidAsOrg],
|
|
793
839
|
[EntityType.SSN]: [suppressCurrency, suppressUnits, suppressReference, suppressMath],
|
|
794
840
|
[EntityType.TAX_ID]: [suppressCurrency, suppressUnits, suppressReference, suppressMath, suppressTaxidAsIpAddress],
|
|
795
|
-
[EntityType.POSTAL_CODE]: [suppressCurrency, suppressUnits, suppressMath, suppressLegal, suppressYearAsPostal, suppressPostalInsideIban, suppressPostalAsHouseNumber, suppressPostalInLongerIdentifier, suppressReference],
|
|
841
|
+
[EntityType.POSTAL_CODE]: [suppressCurrency, suppressUnits, suppressMath, suppressLegal, suppressYearAsPostal, suppressPostalAfterDisqualifyingWord, suppressPostalInsideIban, suppressPostalAsHouseNumber, suppressPostalInLongerIdentifier, suppressReference],
|
|
796
842
|
[EntityType.BIC]: [suppressBicWithoutEvidence],
|
|
797
843
|
[EntityType.BANK_ACCOUNT]: [suppressReference],
|
|
798
844
|
[EntityType.LICENSE_PLATE]: [suppressPlateInCompound, suppressDePlateUnknownDistrict, suppressPlateAsCurrencyAmount],
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -32,6 +32,16 @@ export declare enum EntityType {
|
|
|
32
32
|
HEALTH_INSURANCE = "HEALTH_INSURANCE",
|
|
33
33
|
HEALTHCARE_PROVIDER = "HEALTHCARE_PROVIDER",
|
|
34
34
|
CHAMBER_OF_COMMERCE = "CHAMBER_OF_COMMERCE",
|
|
35
|
+
/**
|
|
36
|
+
* Employee, badge or customer number tied to a person.
|
|
37
|
+
*
|
|
38
|
+
* Emitted **only** when an explicit label names it — see `rules/cues.ts`.
|
|
39
|
+
* There is no pattern for an internal identifier because there is no shape
|
|
40
|
+
* for one; without the label, a digit run is not distinguishable from any
|
|
41
|
+
* other. The type exists so that a labelled employee number is filed
|
|
42
|
+
* correctly instead of being claimed by the phone pattern.
|
|
43
|
+
*/
|
|
44
|
+
INTERNAL_ID = "INTERNAL_ID",
|
|
35
45
|
IMEI = "IMEI",
|
|
36
46
|
GPS_COORDINATES = "GPS_COORDINATES",
|
|
37
47
|
UUID = "UUID",
|
package/dist/esm/types.js
CHANGED
|
@@ -33,6 +33,16 @@ export var EntityType;
|
|
|
33
33
|
EntityType["HEALTH_INSURANCE"] = "HEALTH_INSURANCE";
|
|
34
34
|
EntityType["HEALTHCARE_PROVIDER"] = "HEALTHCARE_PROVIDER";
|
|
35
35
|
EntityType["CHAMBER_OF_COMMERCE"] = "CHAMBER_OF_COMMERCE";
|
|
36
|
+
/**
|
|
37
|
+
* Employee, badge or customer number tied to a person.
|
|
38
|
+
*
|
|
39
|
+
* Emitted **only** when an explicit label names it — see `rules/cues.ts`.
|
|
40
|
+
* There is no pattern for an internal identifier because there is no shape
|
|
41
|
+
* for one; without the label, a digit run is not distinguishable from any
|
|
42
|
+
* other. The type exists so that a labelled employee number is filed
|
|
43
|
+
* correctly instead of being claimed by the phone pattern.
|
|
44
|
+
*/
|
|
45
|
+
EntityType["INTERNAL_ID"] = "INTERNAL_ID";
|
|
36
46
|
EntityType["IMEI"] = "IMEI";
|
|
37
47
|
EntityType["GPS_COORDINATES"] = "GPS_COORDINATES";
|
|
38
48
|
EntityType["UUID"] = "UUID";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "euredact",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "European PII redaction SDK -- fast, zero-dependency, rule-based detection and redaction of personal data across 31 European countries",
|
|
5
5
|
"homepage": "https://euredact.dev",
|
|
6
6
|
"license": "Apache-2.0",
|