@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.
Files changed (3) hide show
  1. package/dist/cli.js +1445 -128
  2. package/dist/core.js +1015 -59
  3. 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 countDigits(value) {
1973
- let n = 0;
1974
- for (const ch of value) {
1975
- if (ch >= "0" && ch <= "9")
1976
- n += 1;
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 n;
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 detectPii(content) {
2385
+ function scanWith(content, rule, scanner, validate, accept) {
2032
2386
  const matches = [];
2033
- for (const rule of RULES) {
2034
- rule.regex.lastIndex = 0;
2035
- let m;
2036
- while ((m = rule.regex.exec(content)) !== null) {
2037
- const group = rule.valueGroup ?? 0;
2038
- const value = m[group];
2039
- if (value === undefined || value.length === 0) {
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
- if (rule.policyId === "pii.phone") {
2043
- const digits = countDigits(value);
2044
- if (digits < 9 || digits > 15) {
2045
- continue;
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
- if (!hasPhoneSeparatorShape(value)) {
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 (containsCalendarDate(value)) {
2051
- continue;
2777
+ if (this.tailEnd - pos < 9) {
2778
+ break;
2052
2779
  }
2053
- if (isIdentifierFragment(content, m.index, m.index + m[0].length)) {
2054
- continue;
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
- if (rule.validate && !rule.validate(value)) {
2058
- continue;
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
- const start = group === 0 ? m.index : m.index + m[0].indexOf(value);
2061
- matches.push({
2062
- category: "pii",
2063
- policyId: rule.policyId,
2064
- severity: rule.severity,
2065
- confidence: rule.confidence,
2066
- start,
2067
- end: start + value.length,
2068
- value,
2069
- mask: rule.mask,
2070
- remediation: "Redact personal data before persisting or publishing."
2071
- });
2072
- if (m.index === rule.regex.lastIndex) {
2073
- rule.regex.lastIndex += 1;
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
- return matches;
2078
- }
2079
- var RULES, CALENDAR_DATE, IDENTIFIER_CHAR, TOKEN_SCAN_LIMIT = 64, UUID_TOKEN;
2080
- var init_pii = __esm(() => {
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: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
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: /(?<![\w.])(\+?\d[\d\s().-]{7,}\d)(?![\w.])/g,
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)\s*:?\s+([A-Z][a-z]+\s+[A-Z][a-z]+)\b/g,
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: /\b[A-Z]{2}\d{2}(?:[ ]?[A-Z0-9]{1,4}){2,8}\b/g,
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: /\b\d{3}-\d{2}-\d{4}\b/g,
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
- const start = Math.max(match.start, cursor);
4438
- if (start >= match.end) {
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 = Math.max(cursor, match.end);
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();