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