@mrciphersmith/keryx 0.3.76 → 0.3.78
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/dist/cli.js +1304 -123
- package/dist/core.js +1014 -59
- package/package.json +3 -3
package/dist/core.js
CHANGED
|
@@ -1969,13 +1969,366 @@ function isValidIp(value) {
|
|
|
1969
1969
|
}
|
|
1970
1970
|
return false;
|
|
1971
1971
|
}
|
|
1972
|
-
function
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1972
|
+
function isWordBoundary(flavour, content, index) {
|
|
1973
|
+
return flavour.wordChar.test(wordCharBefore(content, index)) !== flavour.wordChar.test(wordCharAt(content, index));
|
|
1974
|
+
}
|
|
1975
|
+
function wordCharBefore(content, index) {
|
|
1976
|
+
if (index <= 0) {
|
|
1977
|
+
return "";
|
|
1978
|
+
}
|
|
1979
|
+
const low = content.charCodeAt(index - 1);
|
|
1980
|
+
if (low >= 56320 && low <= 57343 && index >= 2) {
|
|
1981
|
+
const high = content.charCodeAt(index - 2);
|
|
1982
|
+
if (high >= 55296 && high <= 56319) {
|
|
1983
|
+
return content.slice(index - 2, index);
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
return content[index - 1];
|
|
1987
|
+
}
|
|
1988
|
+
function wordCharAt(content, index) {
|
|
1989
|
+
const code = content.codePointAt(index);
|
|
1990
|
+
return code === undefined ? "" : String.fromCodePoint(code);
|
|
1991
|
+
}
|
|
1992
|
+
function execEmail(flavour, content, from) {
|
|
1993
|
+
let at = content.indexOf("@", from);
|
|
1994
|
+
while (at !== -1) {
|
|
1995
|
+
let start = at;
|
|
1996
|
+
while (start > from && flavour.localChar.test(wordCharBefore(content, start))) {
|
|
1997
|
+
start -= wordCharBefore(content, start).length;
|
|
1998
|
+
}
|
|
1999
|
+
while (start < at && !isWordBoundary(flavour, content, start)) {
|
|
2000
|
+
start += wordCharAt(content, start).length;
|
|
2001
|
+
}
|
|
2002
|
+
if (start < at) {
|
|
2003
|
+
flavour.from.lastIndex = start;
|
|
2004
|
+
const m = flavour.from.exec(content);
|
|
2005
|
+
if (m !== null) {
|
|
2006
|
+
return m;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
at = content.indexOf("@", at + 1);
|
|
2010
|
+
}
|
|
2011
|
+
return null;
|
|
2012
|
+
}
|
|
2013
|
+
function emailScannerFor(flavour) {
|
|
2014
|
+
return {
|
|
2015
|
+
lastIndex: 0,
|
|
2016
|
+
exec(content) {
|
|
2017
|
+
const m = execEmail(flavour, content, this.lastIndex);
|
|
2018
|
+
this.lastIndex = m === null ? 0 : m.index + m[0].length;
|
|
2019
|
+
return m;
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
}
|
|
2023
|
+
function execPhone(content, from) {
|
|
2024
|
+
let pos = from;
|
|
2025
|
+
while (pos < content.length) {
|
|
2026
|
+
const digitAt = content[pos] === "+" ? pos + 1 : pos;
|
|
2027
|
+
if (!DIGIT.test(content[digitAt] ?? "") || pos > 0 && PHONE_START_BLOCKER.test(content[pos - 1])) {
|
|
2028
|
+
pos += 1;
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
2031
|
+
PHONE_FROM.lastIndex = pos;
|
|
2032
|
+
const m = PHONE_FROM.exec(content);
|
|
2033
|
+
if (m !== null) {
|
|
2034
|
+
return m;
|
|
2035
|
+
}
|
|
2036
|
+
pos = digitAt + 1;
|
|
2037
|
+
while (pos < content.length && PHONE_RUN_CHAR.test(content[pos])) {
|
|
2038
|
+
pos += 1;
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
function phoneVerdict(content, start, end) {
|
|
2044
|
+
let digits = 0;
|
|
2045
|
+
for (let i = start;i < end && digits <= PHONE_MAX_DIGITS; i += 1) {
|
|
2046
|
+
if (DIGIT.test(content[i])) {
|
|
2047
|
+
digits += 1;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
if (digits < PHONE_MIN_DIGITS || digits > PHONE_MAX_DIGITS) {
|
|
2051
|
+
return false;
|
|
2052
|
+
}
|
|
2053
|
+
const value = content.slice(start, end);
|
|
2054
|
+
return hasPhoneSeparatorShape(value) && !containsCalendarDate(value) && !isIdentifierFragment(content, start, end);
|
|
2055
|
+
}
|
|
2056
|
+
function hasPhoneEvidence(value, singleToken) {
|
|
2057
|
+
return PHONE_SEPARATOR.test(value) || singleToken && value.includes(".") || PHONE_NANP_SPACED.test(value);
|
|
2058
|
+
}
|
|
2059
|
+
function phoneTokens(content, from, to) {
|
|
2060
|
+
const tokens = [];
|
|
2061
|
+
let i = from;
|
|
2062
|
+
while (i < to) {
|
|
2063
|
+
const gapStart = i;
|
|
2064
|
+
while (i < to && WHITESPACE.test(content[i])) {
|
|
2065
|
+
i += 1;
|
|
2066
|
+
}
|
|
2067
|
+
if (i >= to) {
|
|
2068
|
+
break;
|
|
2069
|
+
}
|
|
2070
|
+
const breakBefore = tokens.length > 0 && (i - gapStart >= 2 || LINE_BREAK.test(content.slice(gapStart, i)));
|
|
2071
|
+
const start = i;
|
|
2072
|
+
let digits = 0;
|
|
2073
|
+
let lastDigit = -1;
|
|
2074
|
+
let coreStart = -1;
|
|
2075
|
+
let leadDigits = 0;
|
|
2076
|
+
while (i < to && !WHITESPACE.test(content[i])) {
|
|
2077
|
+
const isDigit = DIGIT.test(content[i]);
|
|
2078
|
+
if (coreStart < 0) {
|
|
2079
|
+
const startsHere = (isDigit || content[i] === "+" && i + 1 < to && DIGIT.test(content[i + 1])) && (i === 0 || !PHONE_START_BLOCKER.test(content[i - 1]));
|
|
2080
|
+
if (startsHere) {
|
|
2081
|
+
coreStart = i;
|
|
2082
|
+
} else if (isDigit) {
|
|
2083
|
+
leadDigits += 1;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
if (isDigit) {
|
|
2087
|
+
digits += 1;
|
|
2088
|
+
lastDigit = i;
|
|
2089
|
+
}
|
|
2090
|
+
i += 1;
|
|
2091
|
+
}
|
|
2092
|
+
const text = content.slice(start, i);
|
|
2093
|
+
const groupable = (PHONE_DIAL_GROUP.test(text) || PHONE_FIRST_GROUP.test(text)) && !containsCalendarDate(text);
|
|
2094
|
+
tokens.push({ start, end: i, coreStart, coreEnd: lastDigit + 1, leadDigits, digits, breakBefore, groupable });
|
|
2095
|
+
}
|
|
2096
|
+
return tokens;
|
|
2097
|
+
}
|
|
2098
|
+
function rescanPhoneRun(content, from, to) {
|
|
2099
|
+
const tokens = phoneTokens(content, from, to);
|
|
2100
|
+
const found = [];
|
|
2101
|
+
const chainAfter = new Array(tokens.length).fill(0);
|
|
2102
|
+
for (let i = tokens.length - 2;i >= 0; i -= 1) {
|
|
2103
|
+
const next = tokens[i + 1];
|
|
2104
|
+
chainAfter[i] = next.groupable && !next.breakBefore ? chainAfter[i + 1] + next.digits : 0;
|
|
2105
|
+
}
|
|
2106
|
+
let first = 0;
|
|
2107
|
+
let resume = -1;
|
|
2108
|
+
while (first < tokens.length) {
|
|
2109
|
+
const head = tokens[first];
|
|
2110
|
+
if (head.coreStart < 0 || head.coreEnd <= head.coreStart) {
|
|
2111
|
+
first += 1;
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
const startsRun = first === 0 || first === resume || head.breakBefore || !tokens[first - 1].groupable;
|
|
2115
|
+
let longest = -1;
|
|
2116
|
+
let clean = -1;
|
|
2117
|
+
const consider = (last) => {
|
|
2118
|
+
const end = tokens[last].coreEnd;
|
|
2119
|
+
if (hasPhoneEvidence(content.slice(head.coreStart, end), last === first) && phoneVerdict(content, head.coreStart, end)) {
|
|
2120
|
+
longest = last;
|
|
2121
|
+
const rest = chainAfter[last];
|
|
2122
|
+
if (rest === 0 || rest >= PHONE_MIN_DIGITS) {
|
|
2123
|
+
clean = last;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
};
|
|
2127
|
+
let digits = head.digits - head.leadDigits;
|
|
2128
|
+
if (digits >= PHONE_MIN_DIGITS) {
|
|
2129
|
+
consider(first);
|
|
2130
|
+
}
|
|
2131
|
+
const headGroup = content.slice(head.coreStart, head.end);
|
|
2132
|
+
let extendable = startsRun && (PHONE_FIRST_GROUP.test(headGroup) || PHONE_DIAL_GROUP.test(headGroup));
|
|
2133
|
+
for (let last = first + 1;last < tokens.length && extendable; last += 1) {
|
|
2134
|
+
const tail = tokens[last];
|
|
2135
|
+
digits += tail.digits;
|
|
2136
|
+
if (digits > PHONE_MAX_DIGITS || tail.digits === 0 || tail.breakBefore) {
|
|
2137
|
+
break;
|
|
2138
|
+
}
|
|
2139
|
+
if (digits >= PHONE_MIN_DIGITS && PHONE_DIAL_GROUP.test(content.slice(tail.start, tail.coreEnd))) {
|
|
2140
|
+
consider(last);
|
|
2141
|
+
}
|
|
2142
|
+
extendable = PHONE_DIAL_GROUP.test(content.slice(tail.start, tail.end));
|
|
2143
|
+
}
|
|
2144
|
+
const best = clean >= 0 ? clean : longest;
|
|
2145
|
+
if (best >= 0) {
|
|
2146
|
+
const value = content.slice(head.coreStart, tokens[best].coreEnd);
|
|
2147
|
+
found.push({ index: head.coreStart, 0: value, 1: value });
|
|
2148
|
+
first = best + 1;
|
|
2149
|
+
resume = first;
|
|
2150
|
+
} else {
|
|
2151
|
+
first += 1;
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
return found;
|
|
2155
|
+
}
|
|
2156
|
+
function joinedPhones(content, from, to) {
|
|
2157
|
+
const found = [];
|
|
2158
|
+
let i = from;
|
|
2159
|
+
while (i < to) {
|
|
2160
|
+
while (i < to && WHITESPACE.test(content[i])) {
|
|
2161
|
+
i += 1;
|
|
2162
|
+
}
|
|
2163
|
+
let zoneEnd = i;
|
|
2164
|
+
let total = 0;
|
|
2165
|
+
while (zoneEnd < to && !WHITESPACE.test(content[zoneEnd])) {
|
|
2166
|
+
if (DIGIT.test(content[zoneEnd])) {
|
|
2167
|
+
total += 1;
|
|
2168
|
+
}
|
|
2169
|
+
zoneEnd += 1;
|
|
2170
|
+
}
|
|
2171
|
+
if (total > PHONE_MAX_DIGITS && !containsCalendarDate(content.slice(i, zoneEnd))) {
|
|
2172
|
+
joinedZone(content, i, zoneEnd, total, found);
|
|
2173
|
+
}
|
|
2174
|
+
i = zoneEnd;
|
|
2175
|
+
}
|
|
2176
|
+
return found;
|
|
2177
|
+
}
|
|
2178
|
+
function isJoinerAt(content, k, to) {
|
|
2179
|
+
const ch = content[k];
|
|
2180
|
+
return (ch === "-" || ch === ".") && k > 0 && DIGIT.test(content[k - 1]) && k + 1 < to && DIGIT.test(content[k + 1]);
|
|
2181
|
+
}
|
|
2182
|
+
function joinedZone(content, zoneStart, zoneEnd, total, found) {
|
|
2183
|
+
let s = zoneStart;
|
|
2184
|
+
let before = 0;
|
|
2185
|
+
for (;; ) {
|
|
2186
|
+
while (s < zoneEnd && !DIGIT.test(content[s]) && !(content[s] === "+" && s + 1 < zoneEnd && DIGIT.test(content[s + 1]))) {
|
|
2187
|
+
s += 1;
|
|
2188
|
+
}
|
|
2189
|
+
if (s >= zoneEnd) {
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
let digits = 0;
|
|
2193
|
+
let lastDigit = s;
|
|
2194
|
+
let open = true;
|
|
2195
|
+
const cuts = [];
|
|
2196
|
+
for (let k = s;k < zoneEnd && open; k += 1) {
|
|
2197
|
+
if (DIGIT.test(content[k])) {
|
|
2198
|
+
digits += 1;
|
|
2199
|
+
lastDigit = k;
|
|
2200
|
+
open = digits <= PHONE_MAX_DIGITS;
|
|
2201
|
+
} else if (digits >= PHONE_MIN_DIGITS && isJoinerAt(content, k, zoneEnd)) {
|
|
2202
|
+
cuts.push({ end: k, digits });
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
if (open && digits >= PHONE_MIN_DIGITS) {
|
|
2206
|
+
cuts.push({ end: lastDigit + 1, digits });
|
|
2207
|
+
}
|
|
2208
|
+
let pick = -1;
|
|
2209
|
+
for (let c = cuts.length - 1;c >= 0; c -= 1) {
|
|
2210
|
+
const cut = cuts[c];
|
|
2211
|
+
const piece = content.slice(s, cut.end);
|
|
2212
|
+
if (!PHONE_JOINED_PIECE.test(piece) || PHONE_CARD_SHAPE.test(piece) || !phoneVerdict(content, s, cut.end)) {
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
if (pick < 0) {
|
|
2216
|
+
pick = c;
|
|
2217
|
+
}
|
|
2218
|
+
const rest = total - before - cut.digits;
|
|
2219
|
+
if (rest === 0 || rest >= PHONE_MIN_DIGITS) {
|
|
2220
|
+
pick = c;
|
|
2221
|
+
break;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
if (pick < 0) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
const cut = cuts[pick];
|
|
2228
|
+
const value = content.slice(s, cut.end);
|
|
2229
|
+
found.push({ index: s, 0: value, 1: value });
|
|
2230
|
+
before += cut.digits;
|
|
2231
|
+
s = cut.end + 1;
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
function withJoined(joined, rescans) {
|
|
2235
|
+
if (joined.length === 0) {
|
|
2236
|
+
return rescans;
|
|
2237
|
+
}
|
|
2238
|
+
const merged = [];
|
|
2239
|
+
let cursor = 0;
|
|
2240
|
+
let pending = 0;
|
|
2241
|
+
for (const rescan of rescans) {
|
|
2242
|
+
const rescanEnd = rescan.index + (rescan[0] ?? "").length;
|
|
2243
|
+
while (cursor < joined.length && joined[cursor].index + (joined[cursor][0] ?? "").length <= rescan.index) {
|
|
2244
|
+
cursor += 1;
|
|
2245
|
+
}
|
|
2246
|
+
if (cursor < joined.length && joined[cursor].index < rescanEnd) {
|
|
2247
|
+
continue;
|
|
2248
|
+
}
|
|
2249
|
+
while (pending < joined.length && joined[pending].index < rescan.index) {
|
|
2250
|
+
merged.push(joined[pending]);
|
|
2251
|
+
pending += 1;
|
|
2252
|
+
}
|
|
2253
|
+
merged.push(rescan);
|
|
2254
|
+
}
|
|
2255
|
+
while (pending < joined.length) {
|
|
2256
|
+
merged.push(joined[pending]);
|
|
2257
|
+
pending += 1;
|
|
2258
|
+
}
|
|
2259
|
+
return merged;
|
|
2260
|
+
}
|
|
2261
|
+
function phoneBaselineEnd(content, runEnd) {
|
|
2262
|
+
for (let e = runEnd;e > 0; e -= 1) {
|
|
2263
|
+
if (DIGIT.test(content[e - 1]) && (e >= content.length || !PHONE_START_BLOCKER.test(content[e]))) {
|
|
2264
|
+
return e;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
return -1;
|
|
2268
|
+
}
|
|
2269
|
+
function isIbanChar(code) {
|
|
2270
|
+
return code >= 65 && code <= 90 || code >= 48 && code <= 57;
|
|
2271
|
+
}
|
|
2272
|
+
function isAsciiWordChar(code) {
|
|
2273
|
+
return isIbanChar(code) || code >= 97 && code <= 122 || code === 95;
|
|
2274
|
+
}
|
|
2275
|
+
function ibanEnd(content, base, pos, groups) {
|
|
2276
|
+
const key = (pos - base) * (IBAN_MAX_GROUPS + 1) + groups;
|
|
2277
|
+
if (ibanFailed[key] === ibanStamp) {
|
|
2278
|
+
return -1;
|
|
2279
|
+
}
|
|
2280
|
+
if (groups < IBAN_MAX_GROUPS) {
|
|
2281
|
+
const from = content.charCodeAt(pos) === 32 ? pos + 1 : pos;
|
|
2282
|
+
let run = 0;
|
|
2283
|
+
while (run < 4 && isIbanChar(content.charCodeAt(from + run))) {
|
|
2284
|
+
run += 1;
|
|
2285
|
+
}
|
|
2286
|
+
for (let length = run;length >= 1; length -= 1) {
|
|
2287
|
+
const end = ibanEnd(content, base, from + length, groups + 1);
|
|
2288
|
+
if (end >= 0) {
|
|
2289
|
+
return end;
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
if (groups >= 2 && !isAsciiWordChar(content.charCodeAt(pos))) {
|
|
2294
|
+
return pos;
|
|
2295
|
+
}
|
|
2296
|
+
ibanFailed[key] = ibanStamp;
|
|
2297
|
+
return -1;
|
|
2298
|
+
}
|
|
2299
|
+
function isCardCandidate(value) {
|
|
2300
|
+
const separators = value.match(CARD_GROUP_SEPARATOR) ?? [];
|
|
2301
|
+
const first = separators[0];
|
|
2302
|
+
if (first === undefined) {
|
|
2303
|
+
return true;
|
|
2304
|
+
}
|
|
2305
|
+
if (!separators.every((separator) => separator === first)) {
|
|
2306
|
+
return false;
|
|
2307
|
+
}
|
|
2308
|
+
const lengths = value.split(first).map((group) => group.length);
|
|
2309
|
+
const last = lengths[lengths.length - 1];
|
|
2310
|
+
if (lengths.length === 3 && lengths[0] === 4 && lengths[1] === 6 && (last === 5 || last === 4)) {
|
|
2311
|
+
return true;
|
|
1977
2312
|
}
|
|
1978
|
-
return
|
|
2313
|
+
return lengths.slice(0, -1).every((length) => length === 4) && last >= 1 && last <= 7;
|
|
2314
|
+
}
|
|
2315
|
+
function hasNonHexGlue(content, start, end) {
|
|
2316
|
+
const before = GLUE_LETTER_BEFORE.exec(content.slice(Math.max(0, start - GLUE_FLANK), start))?.[0] ?? "";
|
|
2317
|
+
const after = GLUE_LETTER_AFTER.exec(content.slice(end, end + GLUE_FLANK))?.[0] ?? "";
|
|
2318
|
+
return before === "" && after === "" || NON_HEX_LETTER.test(before) || NON_HEX_LETTER.test(after);
|
|
2319
|
+
}
|
|
2320
|
+
function outsideUuid(content, start, end) {
|
|
2321
|
+
const from = Math.max(0, start - UUID_REACH);
|
|
2322
|
+
const window = content.slice(from, Math.min(content.length, end + UUID_REACH));
|
|
2323
|
+
UUID_ANYWHERE.lastIndex = 0;
|
|
2324
|
+
let m;
|
|
2325
|
+
while ((m = UUID_ANYWHERE.exec(window)) !== null) {
|
|
2326
|
+
if (from + m.index < end && from + m.index + m[0].length > start) {
|
|
2327
|
+
return false;
|
|
2328
|
+
}
|
|
2329
|
+
UUID_ANYWHERE.lastIndex = m.index + 1;
|
|
2330
|
+
}
|
|
2331
|
+
return true;
|
|
1979
2332
|
}
|
|
1980
2333
|
function containsCalendarDate(value) {
|
|
1981
2334
|
return CALENDAR_DATE.test(value);
|
|
@@ -2028,71 +2381,644 @@ function hasPhoneSeparatorShape(value) {
|
|
|
2028
2381
|
return /^\(?\d{2,4}\)?(?:[-.]\d{2,4})*$/.test(group);
|
|
2029
2382
|
});
|
|
2030
2383
|
}
|
|
2031
|
-
function
|
|
2384
|
+
function scanWith(content, rule, scanner, validate, accept) {
|
|
2032
2385
|
const matches = [];
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2386
|
+
scanner.reset?.();
|
|
2387
|
+
scanner.lastIndex = 0;
|
|
2388
|
+
let m;
|
|
2389
|
+
while ((m = scanner.exec(content)) !== null) {
|
|
2390
|
+
const group = rule.valueGroup ?? 0;
|
|
2391
|
+
const value = m[group];
|
|
2392
|
+
if (value === undefined || value.length === 0) {
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
if (validate && !validate(value)) {
|
|
2396
|
+
continue;
|
|
2397
|
+
}
|
|
2398
|
+
const start = group === 0 ? m.index : m.index + m[0].indexOf(value);
|
|
2399
|
+
const end = start + value.length;
|
|
2400
|
+
if (rule.accept && !rule.accept(content, start, end) || accept && !accept(content, start, end)) {
|
|
2401
|
+
if (m.index === scanner.lastIndex) {
|
|
2402
|
+
scanner.lastIndex += 1;
|
|
2403
|
+
}
|
|
2404
|
+
continue;
|
|
2405
|
+
}
|
|
2406
|
+
const match = {
|
|
2407
|
+
category: "pii",
|
|
2408
|
+
policyId: rule.policyId,
|
|
2409
|
+
severity: rule.severity,
|
|
2410
|
+
confidence: rule.confidence,
|
|
2411
|
+
start,
|
|
2412
|
+
end,
|
|
2413
|
+
value,
|
|
2414
|
+
mask: rule.mask,
|
|
2415
|
+
remediation: "Redact personal data before persisting or publishing."
|
|
2416
|
+
};
|
|
2417
|
+
if (rescanned.has(m)) {
|
|
2418
|
+
rescanTag.add(match);
|
|
2419
|
+
}
|
|
2420
|
+
matches.push(match);
|
|
2421
|
+
if (m.index === scanner.lastIndex) {
|
|
2422
|
+
scanner.lastIndex += 1;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
return matches;
|
|
2426
|
+
}
|
|
2427
|
+
function scanRule(content, rule) {
|
|
2428
|
+
const matches = scanWith(content, rule, rule.regex, rule.validate, undefined);
|
|
2429
|
+
if (rule.also === undefined) {
|
|
2430
|
+
return matches;
|
|
2431
|
+
}
|
|
2432
|
+
let result = matches;
|
|
2433
|
+
for (const extra of rule.also) {
|
|
2434
|
+
const found = scanWith(content, rule, extra.regex, extra.validate ?? rule.validate, extra.accept);
|
|
2435
|
+
const added = found.filter((match) => !containedIn(result, match.start, match.end));
|
|
2436
|
+
if (added.length > 0) {
|
|
2437
|
+
result = coalesce(content, [...result, ...added].sort((x, y) => x.start - y.start || y.end - x.end));
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
return result;
|
|
2441
|
+
}
|
|
2442
|
+
function scanRules(content) {
|
|
2443
|
+
return RULES.map((rule) => scanRule(content, rule));
|
|
2444
|
+
}
|
|
2445
|
+
function decimalDigitValue(codePoint) {
|
|
2446
|
+
if (codePoint < 1632) {
|
|
2447
|
+
return -1;
|
|
2448
|
+
}
|
|
2449
|
+
const cached = digitValues.get(codePoint);
|
|
2450
|
+
if (cached !== undefined) {
|
|
2451
|
+
return cached;
|
|
2452
|
+
}
|
|
2453
|
+
let value = -1;
|
|
2454
|
+
if (DECIMAL_DIGIT.test(String.fromCodePoint(codePoint))) {
|
|
2455
|
+
let runStart = codePoint;
|
|
2456
|
+
while (runStart > 0 && DECIMAL_DIGIT.test(String.fromCodePoint(runStart - 1))) {
|
|
2457
|
+
runStart -= 1;
|
|
2458
|
+
}
|
|
2459
|
+
value = (codePoint - runStart) % 10;
|
|
2460
|
+
}
|
|
2461
|
+
digitValues.set(codePoint, value);
|
|
2462
|
+
return value;
|
|
2463
|
+
}
|
|
2464
|
+
function isHyphenLookalike(code) {
|
|
2465
|
+
return code >= 8208 && code <= 8213 || code === 8722 || code === 8315 || code === 8331 || code === 65112 || code === 65123 || code === 65293 || code === 1418 || code === 1470 || code === 6150 || code === 8259 || code === 12540 || code === 12448 || code === 11834 || code === 11835;
|
|
2466
|
+
}
|
|
2467
|
+
function isInvisible(code) {
|
|
2468
|
+
return code === 173 || code >= 8203 && code <= 8205 || code >= 8288 && code <= 8292 || code === 6158 || code === 847 || code === 8206 || code === 8207;
|
|
2469
|
+
}
|
|
2470
|
+
function foldNumberChar(code, options) {
|
|
2471
|
+
if (isInvisible(code)) {
|
|
2472
|
+
return code === 173 && options.shyHyphen ? "-" : "";
|
|
2473
|
+
}
|
|
2474
|
+
if (code === 160 || code === 5760 || code >= 8192 && code <= 8202 || code === 8232 || code === 8233 || code === 8239 || code === 8287 || code === 12288 || code === 65279) {
|
|
2475
|
+
return " ";
|
|
2476
|
+
}
|
|
2477
|
+
if (isHyphenLookalike(code)) {
|
|
2478
|
+
return "-";
|
|
2479
|
+
}
|
|
2480
|
+
if (code === 8228 || code === 65106 || code === 65294 || code === 12290 || code === 65377) {
|
|
2481
|
+
return ".";
|
|
2482
|
+
}
|
|
2483
|
+
if (code === 65312 || code === 65131) {
|
|
2484
|
+
return "@";
|
|
2485
|
+
}
|
|
2486
|
+
if (code === 65291 || code === 8314 || code === 8330 || code === 65122) {
|
|
2487
|
+
return "+";
|
|
2488
|
+
}
|
|
2489
|
+
if (code === 65288 || code === 8317 || code === 8333 || code === 65113) {
|
|
2490
|
+
return "(";
|
|
2491
|
+
}
|
|
2492
|
+
if (code === 65289 || code === 8318 || code === 8334 || code === 65114) {
|
|
2493
|
+
return ")";
|
|
2494
|
+
}
|
|
2495
|
+
if (options.letters && code >= 65313 && code <= 65338) {
|
|
2496
|
+
return String.fromCharCode(code - 65313 + 65);
|
|
2497
|
+
}
|
|
2498
|
+
const digit = decimalDigitValue(code);
|
|
2499
|
+
return digit < 0 ? null : String(digit);
|
|
2500
|
+
}
|
|
2501
|
+
function isNumberBreak(content, from, to) {
|
|
2502
|
+
const before = content[from - 1];
|
|
2503
|
+
const after = content[to];
|
|
2504
|
+
return from > 1 && (before === "-" || before === ".") && DIGIT.test(content[from - 2]) && DIGIT.test(after ?? "") || from > 0 && DIGIT.test(before ?? "") && (after === "-" || after === ".") && DIGIT.test(content[to + 1] ?? "");
|
|
2505
|
+
}
|
|
2506
|
+
function normalizeWindow(content, from, to, options) {
|
|
2507
|
+
const origin = new Int32Array(to - from + 1);
|
|
2508
|
+
const parts = [];
|
|
2509
|
+
let length = 0;
|
|
2510
|
+
let flushed = from;
|
|
2511
|
+
let changed = false;
|
|
2512
|
+
for (let i = from;i < to; ) {
|
|
2513
|
+
const code = content.charCodeAt(i);
|
|
2514
|
+
if (code < 128) {
|
|
2515
|
+
if (options.breaks && (code === 9 || code === 10 || code === 13)) {
|
|
2516
|
+
let j = i + 1;
|
|
2517
|
+
while (j < to && BREAK_CHAR.test(content[j])) {
|
|
2518
|
+
j += 1;
|
|
2519
|
+
}
|
|
2520
|
+
if (isNumberBreak(content, i, j)) {
|
|
2521
|
+
changed = true;
|
|
2522
|
+
parts.push(content.slice(flushed, i));
|
|
2523
|
+
flushed = j;
|
|
2524
|
+
} else {
|
|
2525
|
+
for (let k = i;k < j; k += 1) {
|
|
2526
|
+
origin[length++] = k;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
i = j;
|
|
2040
2530
|
continue;
|
|
2041
2531
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2532
|
+
origin[length++] = i;
|
|
2533
|
+
i += 1;
|
|
2534
|
+
continue;
|
|
2535
|
+
}
|
|
2536
|
+
const codePoint = content.codePointAt(i);
|
|
2537
|
+
const width = codePoint > 65535 ? 2 : 1;
|
|
2538
|
+
const folded = foldNumberChar(codePoint, options);
|
|
2539
|
+
if (folded === null) {
|
|
2540
|
+
for (let k = 0;k < width; k += 1) {
|
|
2541
|
+
origin[length++] = i;
|
|
2542
|
+
}
|
|
2543
|
+
} else {
|
|
2544
|
+
changed = true;
|
|
2545
|
+
parts.push(content.slice(flushed, i));
|
|
2546
|
+
flushed = i + width;
|
|
2547
|
+
if (folded !== "") {
|
|
2548
|
+
parts.push(folded);
|
|
2549
|
+
origin[length++] = i;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
i += width;
|
|
2553
|
+
}
|
|
2554
|
+
if (!changed) {
|
|
2555
|
+
return null;
|
|
2556
|
+
}
|
|
2557
|
+
parts.push(content.slice(flushed, to));
|
|
2558
|
+
origin[length] = to;
|
|
2559
|
+
return { text: parts.join(""), origin: origin.subarray(0, length + 1) };
|
|
2560
|
+
}
|
|
2561
|
+
function toOriginal(window, content, start, end) {
|
|
2562
|
+
const last = window.origin[end - 1];
|
|
2563
|
+
const width = content.codePointAt(last) > 65535 ? 2 : 1;
|
|
2564
|
+
return [window.origin[start], last + width];
|
|
2565
|
+
}
|
|
2566
|
+
function containedIn(spans, start, end) {
|
|
2567
|
+
let low = 0;
|
|
2568
|
+
let high = spans.length - 1;
|
|
2569
|
+
while (low <= high) {
|
|
2570
|
+
const mid = low + high >> 1;
|
|
2571
|
+
if (spans[mid].start <= start) {
|
|
2572
|
+
low = mid + 1;
|
|
2573
|
+
} else {
|
|
2574
|
+
high = mid - 1;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
const candidate = spans[high];
|
|
2578
|
+
return candidate !== undefined && candidate.start <= start && candidate.end >= end;
|
|
2579
|
+
}
|
|
2580
|
+
function coalesce(content, spans) {
|
|
2581
|
+
const merged = [];
|
|
2582
|
+
for (const span of spans) {
|
|
2583
|
+
const last = merged[merged.length - 1];
|
|
2584
|
+
if (last === undefined || span.start >= last.end) {
|
|
2585
|
+
merged.push(span);
|
|
2586
|
+
continue;
|
|
2587
|
+
}
|
|
2588
|
+
const end = Math.max(last.end, span.end);
|
|
2589
|
+
const union = { ...last, end, value: content.slice(last.start, end) };
|
|
2590
|
+
if (rescanTag.has(last) && rescanTag.has(span)) {
|
|
2591
|
+
rescanTag.add(union);
|
|
2592
|
+
}
|
|
2593
|
+
merged[merged.length - 1] = union;
|
|
2594
|
+
}
|
|
2595
|
+
return merged;
|
|
2596
|
+
}
|
|
2597
|
+
function isUniformCard(card) {
|
|
2598
|
+
const separators = new Set(card.value.replace(/\d/g, ""));
|
|
2599
|
+
return separators.size <= 1;
|
|
2600
|
+
}
|
|
2601
|
+
function dropRescansInsideCards(spans, cards) {
|
|
2602
|
+
if (cards.length === 0 || !spans.some((span) => rescanTag.has(span))) {
|
|
2603
|
+
return spans;
|
|
2604
|
+
}
|
|
2605
|
+
const sorted = cards.filter(isUniformCard).sort((a, b) => a.start - b.start);
|
|
2606
|
+
const reach = [];
|
|
2607
|
+
let farthest = 0;
|
|
2608
|
+
for (const card of sorted) {
|
|
2609
|
+
farthest = Math.max(farthest, card.end);
|
|
2610
|
+
reach.push(farthest);
|
|
2611
|
+
}
|
|
2612
|
+
return spans.filter((span) => {
|
|
2613
|
+
if (!rescanTag.has(span)) {
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
let low = 0;
|
|
2617
|
+
let high = sorted.length - 1;
|
|
2618
|
+
while (low <= high) {
|
|
2619
|
+
const mid = low + high >> 1;
|
|
2620
|
+
if (sorted[mid].start < span.end) {
|
|
2621
|
+
low = mid + 1;
|
|
2622
|
+
} else {
|
|
2623
|
+
high = mid - 1;
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
return high < 0 || reach[high] <= span.start;
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
function viewsOf(content, nonAscii) {
|
|
2630
|
+
const views = [];
|
|
2631
|
+
if (nonAscii) {
|
|
2632
|
+
views.push({});
|
|
2633
|
+
if (FULLWIDTH_CAPITAL.test(content)) {
|
|
2634
|
+
views.push({ letters: true });
|
|
2635
|
+
}
|
|
2636
|
+
if (SOFT_HYPHEN.test(content)) {
|
|
2637
|
+
views.push({ shyHyphen: true });
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
if (NUMBER_BREAK.test(content)) {
|
|
2641
|
+
views.push({ breaks: true });
|
|
2642
|
+
}
|
|
2643
|
+
return views;
|
|
2644
|
+
}
|
|
2645
|
+
function detectPii(content) {
|
|
2646
|
+
const perRule = scanRules(content);
|
|
2647
|
+
const nonAscii = NON_ASCII.test(content);
|
|
2648
|
+
if (nonAscii) {
|
|
2649
|
+
const emailRule = RULES[0];
|
|
2650
|
+
const ascii = scanRule(content, { ...emailRule, regex: asciiEmailScanner });
|
|
2651
|
+
const unicode = perRule[0];
|
|
2652
|
+
const extraEmails = ascii.filter((match) => !containedIn(unicode, match.start, match.end));
|
|
2653
|
+
if (extraEmails.length > 0) {
|
|
2654
|
+
perRule[0] = [...unicode, ...extraEmails].sort((x, y) => x.start - y.start || y.end - x.end);
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
const views = viewsOf(content, nonAscii);
|
|
2658
|
+
for (const options of views) {
|
|
2659
|
+
for (let from = 0;from < content.length; ) {
|
|
2660
|
+
let start = from;
|
|
2661
|
+
let to = Math.min(content.length, from + NORMALIZE_CHUNK);
|
|
2662
|
+
if (start > 0 && (content.charCodeAt(start) & 64512) === 56320) {
|
|
2663
|
+
start -= 1;
|
|
2664
|
+
}
|
|
2665
|
+
if (to < content.length && (content.charCodeAt(to - 1) & 64512) === 55296) {
|
|
2666
|
+
to += 1;
|
|
2667
|
+
}
|
|
2668
|
+
const window = normalizeWindow(content, start, to, options);
|
|
2669
|
+
if (window !== null) {
|
|
2670
|
+
const extra = scanRules(window.text);
|
|
2671
|
+
extra.forEach((found, index) => {
|
|
2672
|
+
if (options.breaks && !NUMBER_RULES.has(index)) {
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
const numbers = NUMBER_RULES.has(index);
|
|
2676
|
+
const base = perRule[index];
|
|
2677
|
+
const added = [];
|
|
2678
|
+
for (const match of found) {
|
|
2679
|
+
const [a, b] = toOriginal(window, content, match.start, match.end);
|
|
2680
|
+
if (!numbers && b - a !== match.end - match.start) {
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
if (start > 0 && a < start + NORMALIZE_SKIP) {
|
|
2684
|
+
continue;
|
|
2685
|
+
}
|
|
2686
|
+
if (!containedIn(base, a, b) && !containedIn(added, a, b)) {
|
|
2687
|
+
const copy = { ...match, start: a, end: b, value: content.slice(a, b) };
|
|
2688
|
+
if (rescanTag.has(match)) {
|
|
2689
|
+
rescanTag.add(copy);
|
|
2690
|
+
}
|
|
2691
|
+
added.push(copy);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (added.length > 0) {
|
|
2695
|
+
perRule[index] = coalesce(content, [...base, ...added].sort((x, y) => x.start - y.start || y.end - x.end));
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
}
|
|
2699
|
+
if (to >= content.length) {
|
|
2700
|
+
break;
|
|
2701
|
+
}
|
|
2702
|
+
from = to - NORMALIZE_OVERLAP;
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
perRule[PHONE_RULE] = dropRescansInsideCards(perRule[PHONE_RULE], perRule[CARD_RULE]);
|
|
2706
|
+
return perRule.flat();
|
|
2707
|
+
}
|
|
2708
|
+
var UNICODE_EMAIL, ASCII_EMAIL, emailScanner, asciiEmailScanner, PHONE_FROM, PHONE_RUN_CHAR, PHONE_START_BLOCKER, DIGIT, PHONE_DIAL_GROUP, PHONE_FIRST_GROUP, PHONE_SEPARATOR, PHONE_NANP_SPACED, WHITESPACE, LINE_BREAK, PHONE_MAX_DIGITS = 15, PHONE_MIN_DIGITS = 9, rescanned, PHONE_JOINED_PIECE, PHONE_CARD_SHAPE, PHONE_GLUE, PHONE_GLUE_LETTER, PHONE_GLUE_EVIDENCE, phoneGlueScanner, phoneScanner, IBAN_START, IBAN_MAX_GROUPS = 8, IBAN_STATES, ibanFailed, ibanStamp = 0, ibanScanner, IBAN_LENGTHS, IBAN_GLUE_START, ibanGluedScanner, CARD_SEPARATOR = "[ \\t\\r\\n._/-]", CARD_EXTRA, CARD_GROUP_SEPARATOR, GLUE_LETTER_BEFORE, GLUE_LETTER_AFTER, NON_HEX_LETTER, GLUE_FLANK = 8, UUID_ANYWHERE, UUID_REACH = 36, SSN_BREAK_EXTRA, IPV4_EXTRA, RULES, CALENDAR_DATE, IDENTIFIER_CHAR, TOKEN_SCAN_LIMIT = 64, UUID_TOKEN, rescanTag, NORMALIZE_CHUNK, NORMALIZE_OVERLAP = 512, NORMALIZE_SKIP = 256, NON_ASCII, DECIMAL_DIGIT, digitValues, BREAK_CHAR, PHONE_RULE, CARD_RULE, NUMBER_RULES, FULLWIDTH_CAPITAL, SOFT_HYPHEN, NUMBER_BREAK;
|
|
2709
|
+
var init_pii = __esm(() => {
|
|
2710
|
+
UNICODE_EMAIL = {
|
|
2711
|
+
from: /(?:(?<=[\p{L}\p{N}\p{M}_])(?![\p{L}\p{N}\p{M}_])|(?<![\p{L}\p{N}\p{M}_])(?=[\p{L}\p{N}\p{M}_]))[\p{L}\p{N}\p{M}._%+-]+@[\p{L}\p{N}\p{M}.-]+\.[\p{L}\p{M}]{2,}(?![\p{L}\p{N}\p{M}_])/uy,
|
|
2712
|
+
localChar: /[\p{L}\p{N}\p{M}._%+-]/u,
|
|
2713
|
+
wordChar: /[\p{L}\p{N}\p{M}_]/u
|
|
2714
|
+
};
|
|
2715
|
+
ASCII_EMAIL = {
|
|
2716
|
+
from: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/y,
|
|
2717
|
+
localChar: /[A-Za-z0-9._%+-]/,
|
|
2718
|
+
wordChar: /\w/
|
|
2719
|
+
};
|
|
2720
|
+
emailScanner = emailScannerFor(UNICODE_EMAIL);
|
|
2721
|
+
asciiEmailScanner = emailScannerFor(ASCII_EMAIL);
|
|
2722
|
+
PHONE_FROM = /(?<![\w.])(\+?\d[\d\s().-]{7,}\d)(?!\w|\.\w)/y;
|
|
2723
|
+
PHONE_RUN_CHAR = /[\d\s().-]/;
|
|
2724
|
+
PHONE_START_BLOCKER = /[\w.]/;
|
|
2725
|
+
DIGIT = /\d/;
|
|
2726
|
+
PHONE_DIAL_GROUP = /^\(?\d{2,4}\)?(?:[-.]\d{2,4})*$/;
|
|
2727
|
+
PHONE_FIRST_GROUP = /^\+?\d{1,4}$/;
|
|
2728
|
+
PHONE_SEPARATOR = /[+()-]/;
|
|
2729
|
+
PHONE_NANP_SPACED = /^(?:\d{1,3} )?\d{3} \d{3} \d{4}$/;
|
|
2730
|
+
WHITESPACE = /\s/;
|
|
2731
|
+
LINE_BREAK = /[\n\r\u2028\u2029]/;
|
|
2732
|
+
rescanned = new WeakSet;
|
|
2733
|
+
PHONE_JOINED_PIECE = /^\+?\d{1,4}(?:[-.]\d{1,4})+$/;
|
|
2734
|
+
PHONE_CARD_SHAPE = /^\d{4}(?:[-.]\d{4})+$/;
|
|
2735
|
+
PHONE_GLUE = /(?<![\d.])\+?\d[\d().-]{7,}\d/g;
|
|
2736
|
+
PHONE_GLUE_LETTER = /[A-Za-z_]/;
|
|
2737
|
+
PHONE_GLUE_EVIDENCE = /[+()-]|\./;
|
|
2738
|
+
phoneGlueScanner = {
|
|
2739
|
+
lastIndex: 0,
|
|
2740
|
+
exec(content) {
|
|
2741
|
+
PHONE_GLUE.lastIndex = this.lastIndex;
|
|
2742
|
+
let m;
|
|
2743
|
+
while ((m = PHONE_GLUE.exec(content)) !== null) {
|
|
2744
|
+
const start = m.index;
|
|
2745
|
+
const end = start + m[0].length;
|
|
2746
|
+
const glued = start > 0 && PHONE_GLUE_LETTER.test(content[start - 1]) || end < content.length && PHONE_GLUE_LETTER.test(content[end]);
|
|
2747
|
+
if (glued && PHONE_GLUE_EVIDENCE.test(m[0]) && phoneVerdict(content, start, end)) {
|
|
2748
|
+
this.lastIndex = end;
|
|
2749
|
+
return { index: start, 0: m[0], 1: m[0] };
|
|
2046
2750
|
}
|
|
2047
|
-
|
|
2751
|
+
}
|
|
2752
|
+
this.lastIndex = 0;
|
|
2753
|
+
return null;
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
phoneScanner = {
|
|
2757
|
+
lastIndex: 0,
|
|
2758
|
+
pending: [],
|
|
2759
|
+
cursor: 0,
|
|
2760
|
+
runEnd: -1,
|
|
2761
|
+
runBaseline: -1,
|
|
2762
|
+
tailEnd: 0,
|
|
2763
|
+
reset() {
|
|
2764
|
+
this.lastIndex = 0;
|
|
2765
|
+
this.pending = [];
|
|
2766
|
+
this.cursor = 0;
|
|
2767
|
+
this.runEnd = -1;
|
|
2768
|
+
this.runBaseline = -1;
|
|
2769
|
+
this.tailEnd = 0;
|
|
2770
|
+
},
|
|
2771
|
+
tail(content) {
|
|
2772
|
+
for (let pos = this.lastIndex;pos < this.tailEnd; pos += 1) {
|
|
2773
|
+
if (!DIGIT.test(content[pos]) || PHONE_START_BLOCKER.test(content[pos - 1])) {
|
|
2048
2774
|
continue;
|
|
2049
2775
|
}
|
|
2050
|
-
if (
|
|
2051
|
-
|
|
2776
|
+
if (this.tailEnd - pos < 9) {
|
|
2777
|
+
break;
|
|
2052
2778
|
}
|
|
2053
|
-
|
|
2054
|
-
|
|
2779
|
+
const value = content.slice(pos, this.tailEnd);
|
|
2780
|
+
return { index: pos, 0: value, 1: value };
|
|
2781
|
+
}
|
|
2782
|
+
this.lastIndex = this.tailEnd;
|
|
2783
|
+
this.tailEnd = 0;
|
|
2784
|
+
return null;
|
|
2785
|
+
},
|
|
2786
|
+
prefix(content, start, end) {
|
|
2787
|
+
if (this.runEnd !== end) {
|
|
2788
|
+
this.runEnd = end;
|
|
2789
|
+
this.runBaseline = phoneBaselineEnd(content, end);
|
|
2790
|
+
}
|
|
2791
|
+
let digits = 0;
|
|
2792
|
+
let limit = end;
|
|
2793
|
+
const ends = [];
|
|
2794
|
+
for (let i = start;i < end; i += 1) {
|
|
2795
|
+
const ch = content[i];
|
|
2796
|
+
if (DIGIT.test(ch)) {
|
|
2797
|
+
digits += 1;
|
|
2798
|
+
if (digits > PHONE_MAX_DIGITS) {
|
|
2799
|
+
limit = i;
|
|
2800
|
+
break;
|
|
2801
|
+
}
|
|
2802
|
+
} else if (WHITESPACE.test(ch) && DIGIT.test(content[i - 1]) && digits >= PHONE_MIN_DIGITS) {
|
|
2803
|
+
ends.push(i);
|
|
2055
2804
|
}
|
|
2056
2805
|
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2806
|
+
const baseline = this.runBaseline;
|
|
2807
|
+
if (baseline < end && baseline >= start + 9 && baseline <= limit && phoneVerdict(content, start, baseline)) {
|
|
2808
|
+
return { end: baseline, baseline: true };
|
|
2059
2809
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2810
|
+
for (let k = ends.length - 1;k >= 0; k -= 1) {
|
|
2811
|
+
const e = ends[k];
|
|
2812
|
+
const value = content.slice(start, e);
|
|
2813
|
+
if (e !== baseline && hasPhoneEvidence(value, !WHITESPACE.test(value)) && phoneVerdict(content, start, e)) {
|
|
2814
|
+
return { end: e, baseline: false };
|
|
2815
|
+
}
|
|
2816
|
+
}
|
|
2817
|
+
return null;
|
|
2818
|
+
},
|
|
2819
|
+
exec(content) {
|
|
2820
|
+
for (;; ) {
|
|
2821
|
+
if (this.cursor < this.pending.length) {
|
|
2822
|
+
return this.pending[this.cursor++];
|
|
2823
|
+
}
|
|
2824
|
+
this.pending = [];
|
|
2825
|
+
this.cursor = 0;
|
|
2826
|
+
let m;
|
|
2827
|
+
if (this.tailEnd > this.lastIndex) {
|
|
2828
|
+
m = this.tail(content);
|
|
2829
|
+
if (m === null) {
|
|
2830
|
+
continue;
|
|
2831
|
+
}
|
|
2832
|
+
} else {
|
|
2833
|
+
m = execPhone(content, this.lastIndex);
|
|
2834
|
+
if (m === null) {
|
|
2835
|
+
this.lastIndex = 0;
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
const end = m.index + (m[0] ?? "").length;
|
|
2840
|
+
this.lastIndex = end;
|
|
2841
|
+
this.tailEnd = 0;
|
|
2842
|
+
if (phoneVerdict(content, m.index, end)) {
|
|
2843
|
+
return m;
|
|
2844
|
+
}
|
|
2845
|
+
const prefix = this.prefix(content, m.index, end);
|
|
2846
|
+
if (prefix !== null) {
|
|
2847
|
+
const value = content.slice(m.index, prefix.end);
|
|
2848
|
+
const found = { index: m.index, 0: value, 1: value };
|
|
2849
|
+
if (!prefix.baseline) {
|
|
2850
|
+
rescanned.add(found);
|
|
2851
|
+
}
|
|
2852
|
+
this.lastIndex = prefix.end;
|
|
2853
|
+
this.tailEnd = end;
|
|
2854
|
+
return found;
|
|
2855
|
+
}
|
|
2856
|
+
this.pending = withJoined(joinedPhones(content, m.index, end), rescanPhoneRun(content, m.index, end));
|
|
2857
|
+
for (const found of this.pending) {
|
|
2858
|
+
rescanned.add(found);
|
|
2859
|
+
}
|
|
2074
2860
|
}
|
|
2075
2861
|
}
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2862
|
+
};
|
|
2863
|
+
IBAN_START = /(?<!\w)[A-Z]{2}\d{2}/g;
|
|
2864
|
+
IBAN_STATES = (4 + IBAN_MAX_GROUPS * 5 + 1) * (IBAN_MAX_GROUPS + 1);
|
|
2865
|
+
ibanFailed = new Uint32Array(IBAN_STATES);
|
|
2866
|
+
ibanScanner = {
|
|
2867
|
+
lastIndex: 0,
|
|
2868
|
+
exec(content) {
|
|
2869
|
+
IBAN_START.lastIndex = this.lastIndex;
|
|
2870
|
+
let m;
|
|
2871
|
+
while ((m = IBAN_START.exec(content)) !== null) {
|
|
2872
|
+
ibanStamp += 1;
|
|
2873
|
+
if (ibanStamp === 4294967295) {
|
|
2874
|
+
ibanFailed.fill(0);
|
|
2875
|
+
ibanStamp = 1;
|
|
2876
|
+
}
|
|
2877
|
+
const end = ibanEnd(content, m.index, m.index + 4, 0);
|
|
2878
|
+
if (end >= 0) {
|
|
2879
|
+
this.lastIndex = end;
|
|
2880
|
+
return { index: m.index, 0: content.slice(m.index, end) };
|
|
2881
|
+
}
|
|
2882
|
+
IBAN_START.lastIndex = m.index + 1;
|
|
2883
|
+
}
|
|
2884
|
+
this.lastIndex = 0;
|
|
2885
|
+
return null;
|
|
2886
|
+
}
|
|
2887
|
+
};
|
|
2888
|
+
IBAN_LENGTHS = {
|
|
2889
|
+
AD: 24,
|
|
2890
|
+
AE: 23,
|
|
2891
|
+
AL: 28,
|
|
2892
|
+
AT: 20,
|
|
2893
|
+
AZ: 28,
|
|
2894
|
+
BA: 20,
|
|
2895
|
+
BE: 16,
|
|
2896
|
+
BG: 22,
|
|
2897
|
+
BH: 22,
|
|
2898
|
+
BR: 29,
|
|
2899
|
+
BY: 28,
|
|
2900
|
+
CH: 21,
|
|
2901
|
+
CR: 22,
|
|
2902
|
+
CY: 28,
|
|
2903
|
+
CZ: 24,
|
|
2904
|
+
DE: 22,
|
|
2905
|
+
DK: 18,
|
|
2906
|
+
DO: 28,
|
|
2907
|
+
EE: 20,
|
|
2908
|
+
EG: 29,
|
|
2909
|
+
ES: 24,
|
|
2910
|
+
FI: 18,
|
|
2911
|
+
FO: 18,
|
|
2912
|
+
FR: 27,
|
|
2913
|
+
GB: 22,
|
|
2914
|
+
GE: 22,
|
|
2915
|
+
GI: 23,
|
|
2916
|
+
GL: 18,
|
|
2917
|
+
GR: 27,
|
|
2918
|
+
GT: 28,
|
|
2919
|
+
HR: 21,
|
|
2920
|
+
HU: 28,
|
|
2921
|
+
IE: 22,
|
|
2922
|
+
IL: 23,
|
|
2923
|
+
IQ: 23,
|
|
2924
|
+
IS: 26,
|
|
2925
|
+
IT: 27,
|
|
2926
|
+
JO: 30,
|
|
2927
|
+
KW: 30,
|
|
2928
|
+
KZ: 20,
|
|
2929
|
+
LB: 28,
|
|
2930
|
+
LC: 32,
|
|
2931
|
+
LI: 21,
|
|
2932
|
+
LT: 20,
|
|
2933
|
+
LU: 20,
|
|
2934
|
+
LV: 21,
|
|
2935
|
+
MC: 27,
|
|
2936
|
+
MD: 24,
|
|
2937
|
+
ME: 22,
|
|
2938
|
+
MK: 19,
|
|
2939
|
+
MR: 27,
|
|
2940
|
+
MT: 31,
|
|
2941
|
+
MU: 30,
|
|
2942
|
+
NL: 18,
|
|
2943
|
+
NO: 15,
|
|
2944
|
+
PK: 24,
|
|
2945
|
+
PL: 28,
|
|
2946
|
+
PS: 29,
|
|
2947
|
+
PT: 25,
|
|
2948
|
+
QA: 29,
|
|
2949
|
+
RO: 24,
|
|
2950
|
+
RS: 22,
|
|
2951
|
+
SA: 24,
|
|
2952
|
+
SC: 31,
|
|
2953
|
+
SE: 24,
|
|
2954
|
+
SI: 19,
|
|
2955
|
+
SK: 24,
|
|
2956
|
+
SM: 27,
|
|
2957
|
+
ST: 25,
|
|
2958
|
+
SV: 28,
|
|
2959
|
+
TL: 23,
|
|
2960
|
+
TN: 24,
|
|
2961
|
+
TR: 26,
|
|
2962
|
+
UA: 29,
|
|
2963
|
+
VA: 22,
|
|
2964
|
+
VG: 24,
|
|
2965
|
+
XK: 20
|
|
2966
|
+
};
|
|
2967
|
+
IBAN_GLUE_START = /[A-Z]{2}\d{2}/g;
|
|
2968
|
+
ibanGluedScanner = {
|
|
2969
|
+
lastIndex: 0,
|
|
2970
|
+
exec(content) {
|
|
2971
|
+
IBAN_GLUE_START.lastIndex = this.lastIndex;
|
|
2972
|
+
let m;
|
|
2973
|
+
while ((m = IBAN_GLUE_START.exec(content)) !== null) {
|
|
2974
|
+
const length = IBAN_LENGTHS[m[0].slice(0, 2)];
|
|
2975
|
+
if (length !== undefined) {
|
|
2976
|
+
let count = 0;
|
|
2977
|
+
let i = m.index;
|
|
2978
|
+
while (i < content.length && count < length) {
|
|
2979
|
+
const code = content.charCodeAt(i);
|
|
2980
|
+
if (isIbanChar(code)) {
|
|
2981
|
+
count += 1;
|
|
2982
|
+
} else if (!(code === 32 && count >= 4 && isIbanChar(content.charCodeAt(i + 1)))) {
|
|
2983
|
+
break;
|
|
2984
|
+
}
|
|
2985
|
+
i += 1;
|
|
2986
|
+
}
|
|
2987
|
+
if (count === length) {
|
|
2988
|
+
this.lastIndex = i;
|
|
2989
|
+
return { index: m.index, 0: content.slice(m.index, i) };
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
IBAN_GLUE_START.lastIndex = m.index + 1;
|
|
2993
|
+
}
|
|
2994
|
+
this.lastIndex = 0;
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
};
|
|
2998
|
+
CARD_EXTRA = new RegExp(`(?<!\\d${CARD_SEPARATOR}{0,2})[2-6](?:${CARD_SEPARATOR}{0,2}\\d){12,18}(?!\\d)(?!${CARD_SEPARATOR}{1,2}\\d)`, "g");
|
|
2999
|
+
CARD_GROUP_SEPARATOR = /[^\d]+/g;
|
|
3000
|
+
GLUE_LETTER_BEFORE = /[A-Za-z_]+$/;
|
|
3001
|
+
GLUE_LETTER_AFTER = /^[A-Za-z_]+/;
|
|
3002
|
+
NON_HEX_LETTER = /[G-Zg-z_]/;
|
|
3003
|
+
UUID_ANYWHERE = /[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}/g;
|
|
3004
|
+
SSN_BREAK_EXTRA = /(?<![0-9])\d{3}(?:\r\n|[\t\r\n])\d{2}(?:\r\n|[\t\r\n])\d{4}(?![0-9])/g;
|
|
3005
|
+
IPV4_EXTRA = /(?<![0-9.])(?<!\b[vV])\d{1,3}(?:[\t\r\n]{0,2}\.[\t\r\n]{0,2}\d{1,3}){3}(?![0-9])/g;
|
|
2081
3006
|
RULES = [
|
|
2082
3007
|
{
|
|
2083
3008
|
policyId: "pii.email",
|
|
2084
3009
|
mask: "email",
|
|
2085
|
-
regex:
|
|
3010
|
+
regex: emailScanner,
|
|
2086
3011
|
severity: "medium",
|
|
2087
3012
|
confidence: 0.85
|
|
2088
3013
|
},
|
|
2089
3014
|
{
|
|
2090
3015
|
policyId: "pii.phone",
|
|
2091
3016
|
mask: "phone",
|
|
2092
|
-
regex:
|
|
3017
|
+
regex: phoneScanner,
|
|
2093
3018
|
severity: "medium",
|
|
2094
3019
|
confidence: 0.7,
|
|
2095
|
-
valueGroup: 1
|
|
3020
|
+
valueGroup: 1,
|
|
3021
|
+
also: [{ regex: phoneGlueScanner }]
|
|
2096
3022
|
},
|
|
2097
3023
|
{
|
|
2098
3024
|
policyId: "pii.address",
|
|
@@ -2104,7 +3030,7 @@ var init_pii = __esm(() => {
|
|
|
2104
3030
|
{
|
|
2105
3031
|
policyId: "pii.person-name",
|
|
2106
3032
|
mask: "name",
|
|
2107
|
-
regex: /\b(?:name\s+is|customer|client|user|patient|employee)
|
|
3033
|
+
regex: /\b(?:name\s+is|customer|client|user|patient|employee)(?:\s*:)?\s+([A-Z][a-z]+\s+[A-Z][a-z]+)\b/g,
|
|
2108
3034
|
severity: "low",
|
|
2109
3035
|
confidence: 0.45,
|
|
2110
3036
|
valueGroup: 1
|
|
@@ -2112,10 +3038,11 @@ var init_pii = __esm(() => {
|
|
|
2112
3038
|
{
|
|
2113
3039
|
policyId: "pii.iban",
|
|
2114
3040
|
mask: "iban",
|
|
2115
|
-
regex:
|
|
3041
|
+
regex: ibanScanner,
|
|
2116
3042
|
severity: "high",
|
|
2117
3043
|
confidence: 0.9,
|
|
2118
|
-
validate: isValidIban
|
|
3044
|
+
validate: isValidIban,
|
|
3045
|
+
also: [{ regex: ibanGluedScanner }]
|
|
2119
3046
|
},
|
|
2120
3047
|
{
|
|
2121
3048
|
policyId: "pii.credit-card",
|
|
@@ -2123,15 +3050,24 @@ var init_pii = __esm(() => {
|
|
|
2123
3050
|
regex: /\b\d(?:[ -]?\d){12,18}\b/g,
|
|
2124
3051
|
severity: "high",
|
|
2125
3052
|
confidence: 0.9,
|
|
2126
|
-
validate: isValidCreditCard
|
|
3053
|
+
validate: isValidCreditCard,
|
|
3054
|
+
accept: outsideUuid,
|
|
3055
|
+
also: [
|
|
3056
|
+
{
|
|
3057
|
+
regex: CARD_EXTRA,
|
|
3058
|
+
validate: (value) => isCardCandidate(value) && isValidCreditCard(value.replace(/\D/g, "")),
|
|
3059
|
+
accept: hasNonHexGlue
|
|
3060
|
+
}
|
|
3061
|
+
]
|
|
2127
3062
|
},
|
|
2128
3063
|
{
|
|
2129
3064
|
policyId: "pii.ssn",
|
|
2130
3065
|
mask: "ssn",
|
|
2131
|
-
regex:
|
|
3066
|
+
regex: /(?<![0-9])\d{3}-\d{2}-\d{4}(?![0-9])/g,
|
|
2132
3067
|
severity: "high",
|
|
2133
3068
|
confidence: 0.85,
|
|
2134
|
-
validate: isValidSsn
|
|
3069
|
+
validate: isValidSsn,
|
|
3070
|
+
also: [{ regex: SSN_BREAK_EXTRA, validate: (value) => isValidSsn(value.replace(/\r\n|[\t\r\n]/g, "-")) }]
|
|
2135
3071
|
},
|
|
2136
3072
|
{
|
|
2137
3073
|
policyId: "pii.ip",
|
|
@@ -2139,12 +3075,25 @@ var init_pii = __esm(() => {
|
|
|
2139
3075
|
regex: /\b(?:(?:\d{1,3}\.){3}\d{1,3}|(?:[0-9A-Fa-f]{1,4}:){2,7}[0-9A-Fa-f]{1,4}|::1)\b/g,
|
|
2140
3076
|
severity: "low",
|
|
2141
3077
|
confidence: 0.6,
|
|
2142
|
-
validate: isValidIp
|
|
3078
|
+
validate: isValidIp,
|
|
3079
|
+
also: [{ regex: IPV4_EXTRA, validate: (value) => isValidIp(value.replace(/[\t\r\n]/g, "")) }]
|
|
2143
3080
|
}
|
|
2144
3081
|
];
|
|
2145
3082
|
CALENDAR_DATE = /(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])/;
|
|
2146
3083
|
IDENTIFIER_CHAR = /[0-9A-Za-z_-]/;
|
|
2147
3084
|
UUID_TOKEN = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/;
|
|
3085
|
+
rescanTag = new WeakSet;
|
|
3086
|
+
NORMALIZE_CHUNK = 1 << 21;
|
|
3087
|
+
NON_ASCII = /[\u0080-\uffff]/;
|
|
3088
|
+
DECIMAL_DIGIT = /\p{Nd}/u;
|
|
3089
|
+
digitValues = new Map;
|
|
3090
|
+
BREAK_CHAR = /[\t\r\n]/;
|
|
3091
|
+
PHONE_RULE = RULES.findIndex((rule) => rule.policyId === "pii.phone");
|
|
3092
|
+
CARD_RULE = RULES.findIndex((rule) => rule.policyId === "pii.credit-card");
|
|
3093
|
+
NUMBER_RULES = new Set(RULES.flatMap((rule, index) => ["pii.phone", "pii.iban", "pii.credit-card", "pii.ssn", "pii.ip"].includes(rule.policyId) ? [index] : []));
|
|
3094
|
+
FULLWIDTH_CAPITAL = /[\uff21-\uff3a]/;
|
|
3095
|
+
SOFT_HYPHEN = /\u00ad/;
|
|
3096
|
+
NUMBER_BREAK = /[-.][\t\r\n]|[\t\r\n][-.]/;
|
|
2148
3097
|
});
|
|
2149
3098
|
|
|
2150
3099
|
// src/security/detect/secrets.ts
|
|
@@ -4430,17 +5379,20 @@ function locationFor(content, match) {
|
|
|
4430
5379
|
};
|
|
4431
5380
|
}
|
|
4432
5381
|
function applyRedaction(content, matches) {
|
|
4433
|
-
const redactable = matches.filter((m) => m.mask !== undefined).sort((a, b) => a.start - b.start);
|
|
5382
|
+
const redactable = matches.filter((m) => m.mask !== undefined).sort((a, b) => a.start - b.start || b.end - a.end);
|
|
4434
5383
|
let out = "";
|
|
4435
5384
|
let cursor = 0;
|
|
4436
5385
|
for (const match of redactable) {
|
|
4437
|
-
|
|
4438
|
-
|
|
5386
|
+
if (match.end <= cursor) {
|
|
5387
|
+
continue;
|
|
5388
|
+
}
|
|
5389
|
+
if (match.start < cursor) {
|
|
5390
|
+
cursor = match.end;
|
|
4439
5391
|
continue;
|
|
4440
5392
|
}
|
|
4441
|
-
out += content.slice(cursor, start);
|
|
5393
|
+
out += content.slice(cursor, match.start);
|
|
4442
5394
|
out += maskFor(match);
|
|
4443
|
-
cursor =
|
|
5395
|
+
cursor = match.end;
|
|
4444
5396
|
}
|
|
4445
5397
|
out += content.slice(cursor);
|
|
4446
5398
|
return out;
|
|
@@ -15840,10 +16792,12 @@ __export(exports_service, {
|
|
|
15840
16792
|
analyze: () => analyze,
|
|
15841
16793
|
appendIncident: () => appendIncident,
|
|
15842
16794
|
applyAuditProposal: () => applyAuditProposal,
|
|
16795
|
+
applyRedaction: () => applyRedaction,
|
|
15843
16796
|
auditGate: () => auditGate,
|
|
15844
16797
|
createSecurityService: () => createSecurityService,
|
|
15845
16798
|
defaultBaselinePath: () => defaultBaselinePath,
|
|
15846
16799
|
detectEntropy: () => detectEntropy,
|
|
16800
|
+
detectPii: () => detectPii,
|
|
15847
16801
|
detectSecrets: () => detectSecrets,
|
|
15848
16802
|
isDeniedForMcpChild: () => isDeniedForMcpChild,
|
|
15849
16803
|
isEntropyBackendEnabled: () => isEntropyBackendEnabled,
|
|
@@ -16109,6 +17063,7 @@ var init_service2 = __esm(() => {
|
|
|
16109
17063
|
init_path_scan();
|
|
16110
17064
|
init_read_source();
|
|
16111
17065
|
init_redact();
|
|
17066
|
+
init_pii();
|
|
16112
17067
|
init_secrets();
|
|
16113
17068
|
init_entropy();
|
|
16114
17069
|
init_incidents();
|