@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.
Files changed (3) hide show
  1. package/dist/cli.js +1304 -123
  2. package/dist/core.js +1014 -59
  3. 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 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
+ 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 n;
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 detectPii(content) {
2384
+ function scanWith(content, rule, scanner, validate, accept) {
2032
2385
  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) {
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
- if (rule.policyId === "pii.phone") {
2043
- const digits = countDigits(value);
2044
- if (digits < 9 || digits > 15) {
2045
- continue;
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
- if (!hasPhoneSeparatorShape(value)) {
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 (containsCalendarDate(value)) {
2051
- continue;
2776
+ if (this.tailEnd - pos < 9) {
2777
+ break;
2052
2778
  }
2053
- if (isIdentifierFragment(content, m.index, m.index + m[0].length)) {
2054
- continue;
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
- if (rule.validate && !rule.validate(value)) {
2058
- continue;
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
- 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;
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
- return matches;
2078
- }
2079
- var RULES, CALENDAR_DATE, IDENTIFIER_CHAR, TOKEN_SCAN_LIMIT = 64, UUID_TOKEN;
2080
- var init_pii = __esm(() => {
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: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
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: /(?<![\w.])(\+?\d[\d\s().-]{7,}\d)(?![\w.])/g,
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)\s*:?\s+([A-Z][a-z]+\s+[A-Z][a-z]+)\b/g,
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: /\b[A-Z]{2}\d{2}(?:[ ]?[A-Z0-9]{1,4}){2,8}\b/g,
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: /\b\d{3}-\d{2}-\d{4}\b/g,
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
- const start = Math.max(match.start, cursor);
4438
- if (start >= match.end) {
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 = Math.max(cursor, match.end);
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();