@form-engine-ts/privacy 2.7.0 → 2.8.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/README.md +3 -1
- package/dist/index.cjs +38 -2
- package/dist/index.js +38 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# @form-engine-ts/privacy
|
|
2
2
|
|
|
3
3
|
Framework-independent sensitive-data candidate detection for form-engine-ts. The standard detector scans only text and
|
|
4
|
-
textarea answers for email addresses, phone numbers, HTTP(S) URLs, and postal codes. Rules and custom detectors are
|
|
4
|
+
textarea answers for email addresses, phone numbers, HTTP(S)/`www.` URLs, and postal codes. Rules and custom detectors are
|
|
5
5
|
injectable, and findings are advisory so applications can choose confirm, block, or audit behavior.
|
|
6
6
|
|
|
7
|
+
Overlapping or contained findings with the same field and type are merged into one source range.
|
|
8
|
+
|
|
7
9
|
```ts
|
|
8
10
|
import { createStandardPrivacyDetector } from "@form-engine-ts/privacy";
|
|
9
11
|
|
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
var STANDARD_RULES = [
|
|
27
27
|
{ type: "email", pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu },
|
|
28
|
-
{ type: "url", pattern: /\
|
|
28
|
+
{ type: "url", pattern: /\b(?:https?:\/\/|www\.)[^\s<>"']*[A-Z0-9/#]/giu },
|
|
29
29
|
{ type: "phone", pattern: /(?:\+?\d[\d\s().-]{6,}\d)/gu },
|
|
30
30
|
{ type: "postal_code", pattern: /(?:〒\s*)?\b\d{3}-?\d{4}\b/gu }
|
|
31
31
|
];
|
|
@@ -57,6 +57,42 @@ function detectRule(fieldId, text, rule) {
|
|
|
57
57
|
}
|
|
58
58
|
return findings;
|
|
59
59
|
}
|
|
60
|
+
function findingsOverlap(left, right) {
|
|
61
|
+
if (left.fieldId !== right.fieldId || left.type !== right.type) return false;
|
|
62
|
+
if (left.start === void 0 || left.end === void 0 || right.start === void 0 || right.end === void 0) {
|
|
63
|
+
return left.matchedText !== void 0 && left.matchedText === right.matchedText;
|
|
64
|
+
}
|
|
65
|
+
return left.start < right.end && right.start < left.end;
|
|
66
|
+
}
|
|
67
|
+
function deduplicateFindings(findings, values) {
|
|
68
|
+
const deduplicated = [];
|
|
69
|
+
for (const finding of findings) {
|
|
70
|
+
const overlappingIndexes = deduplicated.flatMap(
|
|
71
|
+
(candidate, index) => findingsOverlap(candidate, finding) ? [index] : []
|
|
72
|
+
);
|
|
73
|
+
if (overlappingIndexes.length === 0) {
|
|
74
|
+
deduplicated.push(finding);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const overlapping = overlappingIndexes.map((index) => deduplicated[index]).filter((value) => value !== void 0);
|
|
78
|
+
const starts = [finding, ...overlapping].flatMap((value) => value.start === void 0 ? [] : [value.start]);
|
|
79
|
+
const ends = [finding, ...overlapping].flatMap((value) => value.end === void 0 ? [] : [value.end]);
|
|
80
|
+
const start = starts.length === 0 ? void 0 : Math.min(...starts);
|
|
81
|
+
const end = ends.length === 0 ? void 0 : Math.max(...ends);
|
|
82
|
+
const text = values[finding.fieldId];
|
|
83
|
+
const merged = {
|
|
84
|
+
fieldId: finding.fieldId,
|
|
85
|
+
type: finding.type,
|
|
86
|
+
...start === void 0 ? {} : { start },
|
|
87
|
+
...end === void 0 ? {} : { end },
|
|
88
|
+
...start !== void 0 && end !== void 0 && typeof text === "string" ? { matchedText: text.slice(start, end) } : finding.matchedText === void 0 ? {} : { matchedText: finding.matchedText }
|
|
89
|
+
};
|
|
90
|
+
const insertionIndex = overlappingIndexes[0] ?? deduplicated.length;
|
|
91
|
+
for (const index of [...overlappingIndexes].sort((left, right) => right - left)) deduplicated.splice(index, 1);
|
|
92
|
+
deduplicated.splice(insertionIndex, 0, merged);
|
|
93
|
+
}
|
|
94
|
+
return deduplicated;
|
|
95
|
+
}
|
|
60
96
|
function createStandardPrivacyDetector(config = {}) {
|
|
61
97
|
const rules = configuredRules(config);
|
|
62
98
|
const customDetectors = [...config.customDetectors ?? []];
|
|
@@ -73,7 +109,7 @@ function createStandardPrivacyDetector(config = {}) {
|
|
|
73
109
|
for (const rule of rules) findings.push(...detectRule(field.id, value, rule));
|
|
74
110
|
for (const detector of customDetectors) findings.push(...detector(field.id, value));
|
|
75
111
|
}
|
|
76
|
-
return findings;
|
|
112
|
+
return deduplicateFindings(findings, values);
|
|
77
113
|
}
|
|
78
114
|
};
|
|
79
115
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
var STANDARD_RULES = [
|
|
3
3
|
{ type: "email", pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu },
|
|
4
|
-
{ type: "url", pattern: /\
|
|
4
|
+
{ type: "url", pattern: /\b(?:https?:\/\/|www\.)[^\s<>"']*[A-Z0-9/#]/giu },
|
|
5
5
|
{ type: "phone", pattern: /(?:\+?\d[\d\s().-]{6,}\d)/gu },
|
|
6
6
|
{ type: "postal_code", pattern: /(?:〒\s*)?\b\d{3}-?\d{4}\b/gu }
|
|
7
7
|
];
|
|
@@ -33,6 +33,42 @@ function detectRule(fieldId, text, rule) {
|
|
|
33
33
|
}
|
|
34
34
|
return findings;
|
|
35
35
|
}
|
|
36
|
+
function findingsOverlap(left, right) {
|
|
37
|
+
if (left.fieldId !== right.fieldId || left.type !== right.type) return false;
|
|
38
|
+
if (left.start === void 0 || left.end === void 0 || right.start === void 0 || right.end === void 0) {
|
|
39
|
+
return left.matchedText !== void 0 && left.matchedText === right.matchedText;
|
|
40
|
+
}
|
|
41
|
+
return left.start < right.end && right.start < left.end;
|
|
42
|
+
}
|
|
43
|
+
function deduplicateFindings(findings, values) {
|
|
44
|
+
const deduplicated = [];
|
|
45
|
+
for (const finding of findings) {
|
|
46
|
+
const overlappingIndexes = deduplicated.flatMap(
|
|
47
|
+
(candidate, index) => findingsOverlap(candidate, finding) ? [index] : []
|
|
48
|
+
);
|
|
49
|
+
if (overlappingIndexes.length === 0) {
|
|
50
|
+
deduplicated.push(finding);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const overlapping = overlappingIndexes.map((index) => deduplicated[index]).filter((value) => value !== void 0);
|
|
54
|
+
const starts = [finding, ...overlapping].flatMap((value) => value.start === void 0 ? [] : [value.start]);
|
|
55
|
+
const ends = [finding, ...overlapping].flatMap((value) => value.end === void 0 ? [] : [value.end]);
|
|
56
|
+
const start = starts.length === 0 ? void 0 : Math.min(...starts);
|
|
57
|
+
const end = ends.length === 0 ? void 0 : Math.max(...ends);
|
|
58
|
+
const text = values[finding.fieldId];
|
|
59
|
+
const merged = {
|
|
60
|
+
fieldId: finding.fieldId,
|
|
61
|
+
type: finding.type,
|
|
62
|
+
...start === void 0 ? {} : { start },
|
|
63
|
+
...end === void 0 ? {} : { end },
|
|
64
|
+
...start !== void 0 && end !== void 0 && typeof text === "string" ? { matchedText: text.slice(start, end) } : finding.matchedText === void 0 ? {} : { matchedText: finding.matchedText }
|
|
65
|
+
};
|
|
66
|
+
const insertionIndex = overlappingIndexes[0] ?? deduplicated.length;
|
|
67
|
+
for (const index of [...overlappingIndexes].sort((left, right) => right - left)) deduplicated.splice(index, 1);
|
|
68
|
+
deduplicated.splice(insertionIndex, 0, merged);
|
|
69
|
+
}
|
|
70
|
+
return deduplicated;
|
|
71
|
+
}
|
|
36
72
|
function createStandardPrivacyDetector(config = {}) {
|
|
37
73
|
const rules = configuredRules(config);
|
|
38
74
|
const customDetectors = [...config.customDetectors ?? []];
|
|
@@ -49,7 +85,7 @@ function createStandardPrivacyDetector(config = {}) {
|
|
|
49
85
|
for (const rule of rules) findings.push(...detectRule(field.id, value, rule));
|
|
50
86
|
for (const detector of customDetectors) findings.push(...detector(field.id, value));
|
|
51
87
|
}
|
|
52
|
-
return findings;
|
|
88
|
+
return deduplicateFindings(findings, values);
|
|
53
89
|
}
|
|
54
90
|
};
|
|
55
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/privacy",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"typescript"
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@form-engine-ts/core": "2.
|
|
41
|
+
"@form-engine-ts/core": "2.8.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --external @form-engine-ts/core",
|