euredact 0.0.1 → 0.2.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/LICENSE +189 -0
- package/README.md +339 -10
- package/dist/cjs/cache.js +56 -0
- package/dist/cjs/index.js +34 -0
- package/dist/cjs/normalizer.js +45 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/rules/countries/index.js +628 -0
- package/dist/cjs/rules/engine.js +129 -0
- package/dist/cjs/rules/matchers.js +56 -0
- package/dist/cjs/rules/structural.js +104 -0
- package/dist/cjs/rules/suppressors.js +211 -0
- package/dist/cjs/rules/validators.js +699 -0
- package/dist/cjs/sdk.js +80 -0
- package/dist/cjs/types.js +42 -0
- package/dist/esm/cache.d.ts +11 -0
- package/dist/esm/cache.js +52 -0
- package/dist/esm/index.d.ts +10 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/normalizer.d.ts +2 -0
- package/dist/esm/normalizer.js +41 -0
- package/dist/esm/rules/countries/index.d.ts +2 -0
- package/dist/esm/rules/countries/index.js +625 -0
- package/dist/esm/rules/engine.d.ts +15 -0
- package/dist/esm/rules/engine.js +125 -0
- package/dist/esm/rules/matchers.d.ts +12 -0
- package/dist/esm/rules/matchers.js +52 -0
- package/dist/esm/rules/structural.d.ts +2 -0
- package/dist/esm/rules/structural.js +101 -0
- package/dist/esm/rules/suppressors.d.ts +9 -0
- package/dist/esm/rules/suppressors.js +208 -0
- package/dist/esm/rules/validators.d.ts +43 -0
- package/dist/esm/rules/validators.js +654 -0
- package/dist/esm/sdk.d.ts +16 -0
- package/dist/esm/sdk.js +76 -0
- package/dist/esm/types.d.ts +65 -0
- package/dist/esm/types.js +39 -0
- package/package.json +55 -9
- package/index.js +0 -3
package/dist/cjs/sdk.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EuRedact = void 0;
|
|
4
|
+
const cache_js_1 = require("./cache.js");
|
|
5
|
+
const normalizer_js_1 = require("./normalizer.js");
|
|
6
|
+
const engine_js_1 = require("./rules/engine.js");
|
|
7
|
+
const types_js_1 = require("./types.js");
|
|
8
|
+
const DATE_TYPES = new Set([types_js_1.EntityType.DOB, types_js_1.EntityType.DATE_OF_DEATH]);
|
|
9
|
+
class ReferentialMapper {
|
|
10
|
+
counters = new Map();
|
|
11
|
+
mapping = new Map();
|
|
12
|
+
getLabel(text, entityType) {
|
|
13
|
+
if (!this.mapping.has(text)) {
|
|
14
|
+
const count = (this.counters.get(entityType) ?? 0) + 1;
|
|
15
|
+
this.counters.set(entityType, count);
|
|
16
|
+
this.mapping.set(text, `${entityType}_${count}`);
|
|
17
|
+
}
|
|
18
|
+
return this.mapping.get(text);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
class EuRedact {
|
|
22
|
+
engine = new engine_js_1.RuleEngine();
|
|
23
|
+
cache = new cache_js_1.ResultCache();
|
|
24
|
+
referentialMapper = new ReferentialMapper();
|
|
25
|
+
addCustomPattern(name, pattern) {
|
|
26
|
+
this.engine.addCustomPattern(name, pattern);
|
|
27
|
+
this.cache.clear();
|
|
28
|
+
}
|
|
29
|
+
redact(text, options = {}) {
|
|
30
|
+
const { countries = null, mode = "rules", referentialIntegrity = false, detectDates = false, cache = true, } = options;
|
|
31
|
+
const [normalizedText, offsetMapping] = (0, normalizer_js_1.normalize)(text);
|
|
32
|
+
const countriesTuple = countries
|
|
33
|
+
? countries.map(c => c.toUpperCase()).sort()
|
|
34
|
+
: ["ALL"];
|
|
35
|
+
const cacheMode = `${mode}|dates=${detectDates}`;
|
|
36
|
+
let cacheKey;
|
|
37
|
+
if (cache) {
|
|
38
|
+
cacheKey = this.cache.key(normalizedText, countriesTuple, cacheMode);
|
|
39
|
+
const cached = this.cache.get(cacheKey);
|
|
40
|
+
if (cached !== null)
|
|
41
|
+
return cached;
|
|
42
|
+
}
|
|
43
|
+
let detections = this.engine.detect(normalizedText, countries);
|
|
44
|
+
if (offsetMapping !== null) {
|
|
45
|
+
detections = detections.map(d => ({
|
|
46
|
+
...d,
|
|
47
|
+
start: (0, normalizer_js_1.mapOffsetToOriginal)(d.start, offsetMapping),
|
|
48
|
+
end: (0, normalizer_js_1.mapOffsetToOriginal)(d.end, offsetMapping),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
if (!detectDates) {
|
|
52
|
+
detections = detections.filter(d => !DATE_TYPES.has(d.entityType));
|
|
53
|
+
}
|
|
54
|
+
detections.sort((a, b) => a.start - b.start || b.end - a.end);
|
|
55
|
+
let redacted = text;
|
|
56
|
+
for (let i = detections.length - 1; i >= 0; i--) {
|
|
57
|
+
const det = detections[i];
|
|
58
|
+
const replacement = referentialIntegrity
|
|
59
|
+
? this.referentialMapper.getLabel(det.text, det.entityType)
|
|
60
|
+
: `[${det.entityType}]`;
|
|
61
|
+
redacted = redacted.slice(0, det.start) + replacement + redacted.slice(det.end);
|
|
62
|
+
}
|
|
63
|
+
const result = {
|
|
64
|
+
redactedText: redacted,
|
|
65
|
+
detections,
|
|
66
|
+
source: "rules",
|
|
67
|
+
degraded: false,
|
|
68
|
+
};
|
|
69
|
+
if (cache && cacheKey) {
|
|
70
|
+
this.cache.put(cacheKey, result);
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
redactBatch(texts, options = {}) {
|
|
75
|
+
const countries = options.countries ?? null;
|
|
76
|
+
this.engine.loadCountries(countries ? countries.map(c => c.toUpperCase()) : null);
|
|
77
|
+
return texts.map(text => this.redact(text, options));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.EuRedact = EuRedact;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DetectionSource = exports.EntityType = void 0;
|
|
4
|
+
var EntityType;
|
|
5
|
+
(function (EntityType) {
|
|
6
|
+
EntityType["NAME"] = "NAME";
|
|
7
|
+
EntityType["ADDRESS"] = "ADDRESS";
|
|
8
|
+
EntityType["IBAN"] = "IBAN";
|
|
9
|
+
EntityType["BIC"] = "BIC";
|
|
10
|
+
EntityType["CREDIT_CARD"] = "CREDIT_CARD";
|
|
11
|
+
EntityType["PHONE"] = "PHONE";
|
|
12
|
+
EntityType["EMAIL"] = "EMAIL";
|
|
13
|
+
EntityType["DOB"] = "DOB";
|
|
14
|
+
EntityType["DATE_OF_DEATH"] = "DATE_OF_DEATH";
|
|
15
|
+
EntityType["NATIONAL_ID"] = "NATIONAL_ID";
|
|
16
|
+
EntityType["SSN"] = "SSN";
|
|
17
|
+
EntityType["TAX_ID"] = "TAX_ID";
|
|
18
|
+
EntityType["PASSPORT"] = "PASSPORT";
|
|
19
|
+
EntityType["DRIVERS_LICENSE"] = "DRIVERS_LICENSE";
|
|
20
|
+
EntityType["RESIDENCE_PERMIT"] = "RESIDENCE_PERMIT";
|
|
21
|
+
EntityType["LICENSE_PLATE"] = "LICENSE_PLATE";
|
|
22
|
+
EntityType["VIN"] = "VIN";
|
|
23
|
+
EntityType["VAT"] = "VAT";
|
|
24
|
+
EntityType["POSTAL_CODE"] = "POSTAL_CODE";
|
|
25
|
+
EntityType["IP_ADDRESS"] = "IP_ADDRESS";
|
|
26
|
+
EntityType["IPV6_ADDRESS"] = "IPV6_ADDRESS";
|
|
27
|
+
EntityType["MAC_ADDRESS"] = "MAC_ADDRESS";
|
|
28
|
+
EntityType["HEALTH_INSURANCE"] = "HEALTH_INSURANCE";
|
|
29
|
+
EntityType["HEALTHCARE_PROVIDER"] = "HEALTHCARE_PROVIDER";
|
|
30
|
+
EntityType["CHAMBER_OF_COMMERCE"] = "CHAMBER_OF_COMMERCE";
|
|
31
|
+
EntityType["IMEI"] = "IMEI";
|
|
32
|
+
EntityType["GPS_COORDINATES"] = "GPS_COORDINATES";
|
|
33
|
+
EntityType["UUID"] = "UUID";
|
|
34
|
+
EntityType["SOCIAL_HANDLE"] = "SOCIAL_HANDLE";
|
|
35
|
+
EntityType["SECRET"] = "SECRET";
|
|
36
|
+
EntityType["OTHER"] = "OTHER";
|
|
37
|
+
})(EntityType || (exports.EntityType = EntityType = {}));
|
|
38
|
+
var DetectionSource;
|
|
39
|
+
(function (DetectionSource) {
|
|
40
|
+
DetectionSource["RULES"] = "rules";
|
|
41
|
+
DetectionSource["CLOUD"] = "cloud";
|
|
42
|
+
})(DetectionSource || (exports.DetectionSource = DetectionSource = {}));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedactResult } from "./types.js";
|
|
2
|
+
export declare class ResultCache {
|
|
3
|
+
private maxsize;
|
|
4
|
+
private enabled;
|
|
5
|
+
private store;
|
|
6
|
+
constructor(maxsize?: number, enabled?: boolean);
|
|
7
|
+
key(text: string, countries: string[], mode: string): string;
|
|
8
|
+
get(key: string): RedactResult | null;
|
|
9
|
+
put(key: string, result: RedactResult): void;
|
|
10
|
+
clear(): void;
|
|
11
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Simple hash function (FNV-1a) - no crypto dependency needed for cache keys
|
|
2
|
+
function fnv1aHash(str) {
|
|
3
|
+
let hash = 0x811c9dc5;
|
|
4
|
+
for (let i = 0; i < str.length; i++) {
|
|
5
|
+
hash ^= str.charCodeAt(i);
|
|
6
|
+
hash = (hash * 0x01000193) >>> 0;
|
|
7
|
+
}
|
|
8
|
+
return hash.toString(16).padStart(8, "0");
|
|
9
|
+
}
|
|
10
|
+
export class ResultCache {
|
|
11
|
+
maxsize;
|
|
12
|
+
enabled;
|
|
13
|
+
store = new Map();
|
|
14
|
+
constructor(maxsize = 1024, enabled = true) {
|
|
15
|
+
this.maxsize = maxsize;
|
|
16
|
+
this.enabled = enabled;
|
|
17
|
+
}
|
|
18
|
+
key(text, countries, mode) {
|
|
19
|
+
const sorted = [...countries].sort();
|
|
20
|
+
const raw = `${text}|${sorted.join("|")}|${mode}`;
|
|
21
|
+
// Use multiple FNV-1a hashes on different slices for reduced collisions
|
|
22
|
+
const h1 = fnv1aHash(raw);
|
|
23
|
+
const h2 = fnv1aHash(raw.slice(Math.floor(raw.length / 2)) + raw.slice(0, Math.floor(raw.length / 2)));
|
|
24
|
+
return h1 + h2;
|
|
25
|
+
}
|
|
26
|
+
get(key) {
|
|
27
|
+
if (!this.enabled)
|
|
28
|
+
return null;
|
|
29
|
+
const result = this.store.get(key);
|
|
30
|
+
if (result !== undefined) {
|
|
31
|
+
// Move to end (LRU)
|
|
32
|
+
this.store.delete(key);
|
|
33
|
+
this.store.set(key, result);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
put(key, result) {
|
|
39
|
+
if (!this.enabled)
|
|
40
|
+
return;
|
|
41
|
+
this.store.delete(key);
|
|
42
|
+
this.store.set(key, result);
|
|
43
|
+
while (this.store.size > this.maxsize) {
|
|
44
|
+
const first = this.store.keys().next().value;
|
|
45
|
+
if (first !== undefined)
|
|
46
|
+
this.store.delete(first);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
clear() {
|
|
50
|
+
this.store.clear();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { EntityType, DetectionSource } from "./types.js";
|
|
2
|
+
export type { Detection, RedactResult, PatternDef, CountryConfig } from "./types.js";
|
|
3
|
+
export { EuRedact, type RedactOptions } from "./sdk.js";
|
|
4
|
+
export { COUNTRY_CONFIGS } from "./rules/countries/index.js";
|
|
5
|
+
import { type RedactOptions } from "./sdk.js";
|
|
6
|
+
import type { RedactResult } from "./types.js";
|
|
7
|
+
export declare function availableCountries(): string[];
|
|
8
|
+
export declare function redact(text: string, options?: RedactOptions): RedactResult;
|
|
9
|
+
export declare function addCustomPattern(name: string, pattern: string): void;
|
|
10
|
+
export declare function redactBatch(texts: string[], options?: RedactOptions): RedactResult[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { EntityType, DetectionSource } from "./types.js";
|
|
2
|
+
export { EuRedact } from "./sdk.js";
|
|
3
|
+
export { COUNTRY_CONFIGS } from "./rules/countries/index.js";
|
|
4
|
+
import { EuRedact } from "./sdk.js";
|
|
5
|
+
import { COUNTRY_CONFIGS } from "./rules/countries/index.js";
|
|
6
|
+
let _instance = null;
|
|
7
|
+
function getInstance() {
|
|
8
|
+
if (_instance === null)
|
|
9
|
+
_instance = new EuRedact();
|
|
10
|
+
return _instance;
|
|
11
|
+
}
|
|
12
|
+
export function availableCountries() {
|
|
13
|
+
return Object.keys(COUNTRY_CONFIGS).filter(c => c !== "SHARED").sort();
|
|
14
|
+
}
|
|
15
|
+
export function redact(text, options) {
|
|
16
|
+
return getInstance().redact(text, options);
|
|
17
|
+
}
|
|
18
|
+
export function addCustomPattern(name, pattern) {
|
|
19
|
+
getInstance().addCustomPattern(name, pattern);
|
|
20
|
+
}
|
|
21
|
+
export function redactBatch(texts, options) {
|
|
22
|
+
return getInstance().redactBatch(texts, options);
|
|
23
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export function normalize(text) {
|
|
2
|
+
const normalized = text.normalize("NFC");
|
|
3
|
+
if (normalized.length === text.length)
|
|
4
|
+
return [normalized, null];
|
|
5
|
+
return [normalized, buildOffsetMapping(text, normalized)];
|
|
6
|
+
}
|
|
7
|
+
function buildOffsetMapping(original, normalized) {
|
|
8
|
+
const nfdOriginal = original.normalize("NFD");
|
|
9
|
+
const nfdNormalized = normalized.normalize("NFD");
|
|
10
|
+
// Build: normalized index -> original index via NFD as intermediate
|
|
11
|
+
const normToOrig = [];
|
|
12
|
+
for (let normIdx = 0; normIdx < normalized.length; normIdx++) {
|
|
13
|
+
const nfdOfNormChar = normalized[normIdx].normalize("NFD");
|
|
14
|
+
let nfdStart = 0;
|
|
15
|
+
for (let i = 0; i < normIdx; i++) {
|
|
16
|
+
nfdStart += normalized[i].normalize("NFD").length;
|
|
17
|
+
}
|
|
18
|
+
let targetOrig = 0;
|
|
19
|
+
let running = 0;
|
|
20
|
+
for (let oi = 0; oi < original.length; oi++) {
|
|
21
|
+
const nfdOc = original[oi].normalize("NFD");
|
|
22
|
+
if (running + nfdOc.length > nfdStart) {
|
|
23
|
+
targetOrig = oi;
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
running += nfdOc.length;
|
|
27
|
+
}
|
|
28
|
+
normToOrig.push(targetOrig);
|
|
29
|
+
}
|
|
30
|
+
return normToOrig;
|
|
31
|
+
}
|
|
32
|
+
export function mapOffsetToOriginal(offset, mapping) {
|
|
33
|
+
if (mapping === null)
|
|
34
|
+
return offset;
|
|
35
|
+
if (offset >= mapping.length) {
|
|
36
|
+
if (mapping.length > 0)
|
|
37
|
+
return mapping[mapping.length - 1] + (offset - mapping.length + 1);
|
|
38
|
+
return offset;
|
|
39
|
+
}
|
|
40
|
+
return mapping[offset];
|
|
41
|
+
}
|