@privacyscrubber/mcp-server 2.3.9 → 2.4.1

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/ps-pii-engine.cjs CHANGED
@@ -8,7 +8,10 @@
8
8
  */
9
9
 
10
10
  (function() {
11
- if (typeof window !== 'undefined' && window.PrivacyScrubberEngine) return;
11
+ if (typeof window !== 'undefined' && window.PrivacyScrubberEngine) {
12
+ if (typeof exports !== 'undefined') module.exports = window.PrivacyScrubberEngine;
13
+ return;
14
+ }
12
15
 
13
16
  // --- START DEFAULT RULES ---
14
17
  /**
@@ -21,14 +24,32 @@ let DEVOPS_SECRETS = [
21
24
  { name: 'Private Cryptographic Key (PEM Block)', type: 'SECRET', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----[\s\S]{10,8192}?-----END (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g },
22
25
  { name: 'Private Cryptographic Key (Header)', type: 'SECRET', regex: /-----BEGIN (?:RSA |EC |PGP |DSA |OPENSSH |ENCRYPTED )?PRIVATE KEY(?: BLOCK)?-----/g },
23
26
 
24
- // Connection URIs & Credentials
27
+ // Connection URIs, Webhooks & Credentials
28
+ { name: 'Slack / Discord Incoming Webhook URL', type: 'SECRET', regex: /\bhttps:\/\/(?:hooks\.slack\.com\/services\/T[A-Z0-9_]+\/B[A-Z0-9_]+\/[A-Za-z0-9_-]+|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d{17,20}\/[A-Za-z0-9_-]{60,70})\b/gi },
29
+ { name: 'Azure Connection String / SAS Token', type: 'SECRET', regex: /(?:DefaultEndpointsProtocol=https?;AccountName=[^;\s"']+;AccountKey=[A-Za-z0-9+/=]{20,}(?:;EndpointSuffix=[^;\s"']+)?|Endpoint=sb:\/\/[^;\s"']+;SharedAccessKeyName=[^;\s"']+;SharedAccessKey=[A-Za-z0-9+/=]{16,}|\bSharedAccessSignature=sr=[^\s"']+&sig=[A-Za-z0-9%/+=]{16,}|\bAccountKey=[A-Za-z0-9+/=]{40,88}==)/gi },
25
30
  { name: 'Database Connection URI', type: 'SECRET', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi },
26
- { name: 'Embedded Credentials URI', type: 'SECRET', regex: /\b(?:https?|s?ftp):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?::\d+)?(?:\/[^\s"']*)?/gi },
31
+ { name: 'Embedded Credentials URI', type: 'SECRET', regex: /\b(?:h(?:tt|xx)ps?|s?f(?:t|x)p|wss?):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?:(?:\[\.\]|\(\.\))[a-zA-Z0-9_.-]+)*(?::\d+)?(?:\/[^\s"']*)?/gi },
32
+ { name: 'Docker Registry Auth / K8s Secret Base64', type: 'SECRET', regex: /(?<=["']?(?:auth|identitytoken|registrytoken|\.dockerconfigjson)["']?\s*:\s*["'])[A-Za-z0-9+/=]{16,}(?=["'])/gi },
27
33
  { name: 'URL Query Parameter Secret', type: 'SECRET', regex: /(?<=[?&](?:api_key|apikey|access_token|client_secret|auth_token|token|secret|password|private_key)=)[^\s"'\x26#)>]{8,}/gi },
28
- { name: 'Bearer / Auth Header Token', type: 'SECRET', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}\b/gi },
34
+ { name: 'CLI Credential Flag / Curl User Auth', type: 'SECRET', regex: /(?:\bcurl\b[^\r\n]*?\s(?:-u|--user)\s+["']?[A-Za-z0-9_.+-]+:[^\s"']+|\b(?:--password|--api-key|--apikey|--access-token|--auth-token|--client-secret|--secret-key|--private-key)(?:=|\s+)["']?[^\s"']{6,}["']?)/gi },
35
+ { name: 'HTTP API Key Header', type: 'SECRET', regex: /\b(?:X-API-Key|X-Amz-Security-Token|X-Functions-Key|X-Shopify-Access-Token|X-Auth-Token|Api-Key)\s*:\s*["']?[A-Za-z0-9_.\-~+/=]{12,}["']?/gi },
36
+ { name: 'Bearer / Auth Header Token', type: 'SECRET', regex: /(?<=\b(?:Bearer|Token)\s+)[a-zA-Z0-9_\-\.~+/=]{16,}(?![a-zA-Z0-9_\-\.~+/=])/gi },
37
+ { name: 'HTTP Basic Auth Token', type: 'SECRET', regex: /(?<=\bAuthorization\s*:\s*Basic\s+)[A-Za-z0-9+/=]{8,}(?![A-Za-z0-9+/=])/gi },
29
38
 
30
39
  // Secrets & API Keys
31
40
  { name: 'AWS Credentials', type: 'SECRET', regex: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|AIPA)[A-Z0-9]{12,28}\b/g },
41
+ { name: 'Google Cloud / Gemini API Key', type: 'SECRET', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g },
42
+ { name: 'Anthropic API Key', type: 'SECRET', regex: /\bsk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}\b/g },
43
+ { name: 'HuggingFace / Perplexity API Key', type: 'SECRET', regex: /\b(?:hf_[a-zA-Z0-9]{20,64}|pplx-[a-zA-Z0-9]{24,64})\b/g },
44
+ { name: 'SendGrid API Key', type: 'SECRET', regex: /\bSG\.[a-zA-Z0-9_-]{16,32}\.[a-zA-Z0-9_-]{32,64}\b/g },
45
+ { name: 'Postman API Key', type: 'SECRET', regex: /\bPMAK-[a-fA-F0-9]{24}-[a-fA-F0-9]{34}\b/g },
46
+ { name: 'GitHub Fine-Grained PAT', type: 'SECRET', regex: /\bgithub_pat_[a-zA-Z0-9]{20,30}_[a-zA-Z0-9]{40,80}\b/g },
47
+ { name: 'Slack Enterprise / Refresh Token', type: 'SECRET', regex: /\bxoxe\.xox[baprs]-\d-[a-zA-Z0-9-]{10,}\b|\bxox[eapbrs]-[a-zA-Z0-9-]{10,}\b/g },
48
+ { name: 'Telegram Bot API Token', type: 'SECRET', regex: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/g },
49
+ { name: 'Age Encryption Secret Key', type: 'SECRET', regex: /\bAGE-SECRET-KEY-1[A-Z0-9]{58}\b/g },
50
+ { name: 'Modern Cloud & AI Platform Key', type: 'SECRET', regex: /\b(?:sbp_[a-fA-F0-9]{40}|dop_v1_[a-fA-F0-9]{64}|doo_v1_[a-fA-F0-9]{64}|dp\.(?:st|pt|ct|sa)\.[a-zA-Z0-9_-]{20,}|lin_api_[a-zA-Z0-9]{32,64}|ntn_[a-zA-Z0-9]{32,64}|secret_[a-zA-Z0-9]{32,64}|gsk_[a-zA-Z0-9]{32,64}|r8_[a-zA-Z0-9]{24,64}|tvly-[a-zA-Z0-9_-]{20,64}|fc-[a-fA-F0-9]{32}|dapi[a-fA-F0-9]{32}(?:-\d)?|re_[a-zA-Z0-9_]{20,64}|pypi-[A-Za-z0-9_-]{16,128}|whsec_[A-Za-z0-9+/=_-]{24,128}|shpat_[a-fA-F0-9]{32}|shpss_[a-fA-F0-9]{32}|shpca_[a-fA-F0-9]{32}|shppa_[a-fA-F0-9]{32}|hvs\.[A-Za-z0-9_-]{24,128}|hvb\.[A-Za-z0-9_-]{24,128}|glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}|glc_[A-Za-z0-9+/=_-]{20,128}|pscale_tkn_[A-Za-z0-9_.-]{20,64}|pscale_pw_[A-Za-z0-9_.-]{20,64}|pat[A-Za-z0-9]{14}\.[a-fA-F0-9]{64}|pat-(?:na1|eu1)-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|sq0atp-[A-Za-z0-9_-]{22,64}|sq0csp-[A-Za-z0-9_-]{32,64}|NRAK-[A-Z0-9]{27}|v1\.0-[a-fA-F0-9]{24}-[a-fA-F0-9]{16,160}|ATATT3xFfGF0[A-Za-z0-9+/=_-]{24,128}|tskey-(?:auth|api|client)-[A-Za-z0-9_-]{20,96}|sntry[su]_[A-Za-z0-9+/=_-]{32,160}|(?:pk|sk)\.eyJ1[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{16,}|key-[a-f0-9]{32}|[a-f0-9]{32}-us\d{1,2}|napi_[a-zA-Z0-9]{32,64}|[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{50,80})\b/g },
51
+ { name: 'Twilio Account SID / API Key', type: 'SECRET', regex: /\b(?:AC|SK)[a-fA-F0-9]{32}\b/g },
52
+ { name: 'Bcrypt / Argon2 Password Hash', type: 'SECRET', regex: /(?<![A-Za-z0-9])(?:\$2[aby]\$\d{2}\$[./A-Za-z0-9=]{20,60}|\$argon2(?:id|i|d)\$[^\s"']{15,120})/g },
32
53
  { name: 'AWS Resource Name (ARN)', type: 'ID', regex: /\barn:(?:aws|aws-cn|aws-us-gov):[a-z0-9-]+:[a-z0-9-]*:(?:\d{12})?:[a-zA-Z0-9-_\/.:*]+\b/gi },
33
54
  { name: 'AWS Account ID', type: 'ID', regex: /(?<=\b(?:accountId|account_id|account-id|AWS\s+Account|Account)\s*[:=]\s*["']?)\d{12}\b/gi },
34
55
  { name: 'GCP Resource / Secret Path', type: 'ID', regex: /\bprojects\/[a-z0-9-]+(?:\/secrets\/[a-zA-Z0-9_.-]+|\/serviceAccounts\/[a-zA-Z0-9_.-]+|\/subscriptions\/[a-zA-Z0-9_.-]+)(?:\/versions\/[a-zA-Z0-9_.-]+)?\b/gi },
@@ -43,7 +64,7 @@ let DEVOPS_SECRETS = [
43
64
  { name: 'Hash / Hex Key (32-64 chars)', type: 'SECRET', regex: /\b[a-fA-F0-9]{32,64}\b/g },
44
65
  { name: 'CVE Identifier', type: 'SECRET', regex: /\bCVE-\d{4}-\d{4,}\b/gi },
45
66
  { name: 'Cryptographic Hash', type: 'SECRET', regex: /\b(?:MD5|SHA1|SHA256)[:\s][a-f0-9]{32,64}\b/gi },
46
- { name: 'Database/API Secret', type: 'SECRET', regex: /(?<![?&])\b(?:(?:DB|POSTGRES|REDIS|MYSQL|DATABASE)[-_]?(?:PASSWORD|PASS|SECRET|KEY|PWD|CREDENTIALS?|TOKEN|AUTH)|AWS[-_]?(?:SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN|SECURITY_TOKEN)|API[-_]?(?:KEY|SECRET|TOKEN|PASS|PASSWORD|AUTH|CREDENTIALS?)|[A-Za-z0-9_]*SECRET[A-Za-z0-9_]*|[A-Za-z0-9_]*PASSWORD[A-Za-z0-9_]*|[A-Za-z0-9_]*PASSWD[A-Za-z0-9_]*|[A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*|ENCRYPTION[-_]KEY)[ \t]*[:=][ \t]*["']?(?:Bearer\s+)?([^\s"'&;\r\n]+)\b["']?/gi },
67
+ { name: 'Database/API Secret', type: 'SECRET', regex: /(?<![?&])\b(?:(?:DB|POSTGRES|REDIS|MYSQL|DATABASE)[-_]?(?:PASSWORD|PASS|SECRET|KEY|PWD|CREDENTIALS?|TOKEN|AUTH)|AWS[-_]?(?:SECRET_ACCESS_KEY|SECRET_KEY|SESSION_TOKEN|SECURITY_TOKEN)|API[-_]?(?:KEY|SECRET|TOKEN|PASS|PASSWORD|AUTH|CREDENTIALS?)|[A-Za-z0-9_]*SECRET[A-Za-z0-9_]*|[A-Za-z0-9_]*PASSWORD[A-Za-z0-9_]*|[A-Za-z0-9_]*PASSWD[A-Za-z0-9_]*|[A-Za-z0-9_]*TOKEN[A-Za-z0-9_]*|ENCRYPTION[-_]KEY)[ \t]*[:=][ \t]*["']?(?:Bearer\s+)?([^\s"'&;\r\n,]+?)(?=["'\s&;\r\n,]|$)/gi },
47
68
  { name: 'Proprietary IP / Confidential', type: 'SECRET', regex: /\b(?:CONFIDENTIAL|PROPRIETARY|TRADE SECRET|DO NOT DISTRIBUTE|INTERNAL USE ONLY)\b/gi }
48
69
  ];
49
70
 
@@ -51,6 +72,8 @@ let REGEX_RULES = [
51
72
  // --- Multi-National PII & National Identifiers (General Profile Base Rules) ---
52
73
  // 1. Israel Phone Number (Mobile, Landline & VoIP)
53
74
  { name: 'Israel Phone Number', type: 'PHONE', regex: /(?<!\w)(?:(?:\+972[-.\s]?|0)(?:5[0-9]|[23489]|7[2346789]))[-.\s]?\d{3}[-.\s]?\d{4}\b/g },
75
+ // 1a. Brazil Phone Number (+55 (11) 98765-4321, (11) 98765-4321, (11) 3456-7890)
76
+ { name: 'Brazil Phone Number', type: 'PHONE', regex: /(?<!\w)(?:(?:\+|00)55[ \t.-]*)?\([1-9]\d\)[ \t.-]*9?\d{4}[ \t.-]*\d{4}\b|(?<!\w)(?:\+|00)55[ \t.-]*[1-9]\d[ \t.-]*9?\d{4}[ \t.-]*\d{4}\b/g },
54
77
  // 1b. European Parenthetical Trunk Zero Phone (+31 (0)6..., +49 (0)171..., +33 (0)6..., +44 (0)20..., +34 (0)...)
55
78
  { name: 'European Parenthetical Trunk Zero Phone', type: 'PHONE', regex: /(?<![A-Za-z0-9./_-])(?:\+|00)(?:31|49|33|44|39|34|972)[ \t\xA0]*\(\s*0\s*\)[ \t\xA0]*[1-9](?:[ \t./-]?\d{1,4}){2,5}\b/g },
56
79
  // 1c. France Phone Number (5 pairs of 2 digits with dots, spaces, or hyphens: 01.23.45.67.89, 06 12 34 56 78, +33 1 23 45 67 89)
@@ -64,14 +87,20 @@ let REGEX_RULES = [
64
87
  // 1g. Spain & Italy Phone Numbers (International & labeled national mobile/landline)
65
88
  { name: 'Spain Phone Number', type: 'PHONE', regex: /(?:(?:\b(?:Tel(?:[ée]fono)?|M[oó]vil|Tlf\.?|Cel\.?)[:\s#=-]+)|(?<!\w)(?:\+|00)34[ \t\xA0]*)([6789]\d{2}[ \t.-]?(?:\d{3}[ \t.-]?\d{3}|\d{2}[ \t.-]?\d{2}[ \t.-]?\d{2}))\b/gi },
66
89
  { name: 'Italy Phone Number', type: 'PHONE', regex: /(?:(?:\b(?:Tel(?:efono)?|Cell(?:ulare)?|Mobile)[:\s#=-]+)|(?<!\w)(?:\+|00)39[ \t\xA0]*)(?:(3\d{2}[ \t.-]?(?:\d{6,7}|\d{3}[ \t.-]?\d{3,4}))|(0\d{1,3}[ \t.-]?\d{6,8}))\b/gi },
67
- // 2. Israel Teudat Zehut (with Mod 10 Luhn validator)
68
- { name: 'Israel National ID (Teudat Zehut)', type: 'ID', validatorType: 'luhn_il', regex: /(?:(?:\b(?:תעודת\s+זהות|ת\.?ז\.?|מספר\s+זהות|Teudat\s+Zehut|TZ)\s*[:#=-]?\s*)|\b(?<!\d))(\d{8,9})\b(?!\d)/giu },
90
+ // 2. Israel Teudat Zehut (with Mod 10 Luhn validator, supporting hyphenated check digit e.g. 01234567-4)
91
+ { name: 'Israel National ID (Teudat Zehut)', type: 'ID', validatorType: 'luhn_il', regex: /(?:(?:(?<=^|[^\p{L}\p{N}_])(?:תעודת\s+זהות|ת\.?ז\.?|ת["״]ז|מספר\s+זהות|מ\.?ז\.?|Teudat\s+Zehut|TZ)\s*[:#=-]?\s*)|\b(?<!\d))(\d{7,8}-\d|\d{8,9})\b(?!\d)/giu },
69
92
  // 3. Israel Company Registration (H.P. / 50-58 prefix with Mod 10 Luhn validator)
70
- { name: 'Israel Company Registration (H.P.)', type: 'ID', validatorType: 'luhn_il', regex: /(?:(?:\b(?:ח\.?פ\.?|ע\.?ר\.?|ע\.?מ\.?|מע"מ|תיק\s+מע"מ|מספר\s+תאגיד|חברה\s+פרטית|עוסק\s+מורשה)\s*[:#=-]?\s*)|\b(?<!\d))(5[0124568]\d{7})\b(?!\d)/giu },
93
+ { name: 'Israel Company Registration (H.P.)', type: 'ID', validatorType: 'luhn_il', regex: /(?:(?:(?<=^|[^\p{L}\p{N}_])(?:ח\.?פ\.?|ח["״]פ|ע\.?ר\.?|ע\.?מ\.?|ע["״]מ|מע["״]מ|תיק\s+מע["״]מ|מספר\s+תאגיד|חברה\s+פרטית|עוסק\s+מורשה)\s*[:#=-]?\s*)|\b(?<!\d))(5[0124568]\d{6}-?\d)\b(?!\d)/giu },
94
+ // 3a0. Israel Vehicle License Plate (7 or 8 digits formatted or labeled)
95
+ { name: 'Israel Vehicle License Plate', type: 'ID', regex: /(?:(?<=^|[^\p{L}\p{N}_])(?:מספר\s+רכב|מס['’]\s+רכב|לוחית\s+רישוי|מספר\s+רישוי)\s*[:#=-]?\s*)(\d{2}-\d{3}-\d{2}|\d{3}-\d{2}-\d{3}|\d{7,8})\b/giu },
96
+ // 3a. Brazil Individual Tax ID (CPF) & Corporate Tax ID (CNPJ) & Identity Card (RG)
97
+ { name: 'Brazil Individual Tax ID (CPF)', type: 'ID', validatorType: 'mod11_br_cpf', regex: /(?:(?:\bCPF(?:\s*(?:n[º°o]\.?|número|num\.?|#))?[^\S\r\n]*[:#=-]?[^\S\r\n]*)|(?<!\d))(\d{3}\.\d{3}\.\d{3}-\d{2}|\d{11})\b(?!\d)/gi },
98
+ { name: 'Brazil Corporate Tax ID (CNPJ)', type: 'ID', validatorType: 'mod11_br_cnpj', regex: /(?:(?:\bCNPJ(?:\s*(?:n[º°o]\.?|número|num\.?|#))?[^\S\r\n]*[:#=-]?[^\S\r\n]*)|(?<!\d))(\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}|\d{14})\b(?!\d)/gi },
99
+ { name: 'Brazil Identity Card (RG)', type: 'ID', regex: /\bRG(?:\s*(?:n[º°o]\.?|número|#))?[^\S\r\n]*[:#=-]?[^\S\r\n]*(\d{1,2}\.?\d{3}\.?\d{3}-?[0-9Xx])\b/gi },
71
100
  // 3b. Israel Domestic Bank Account Details (Bank, Branch, Account tripartite / בנק, סניף, חשבון)
72
101
  { name: 'Israel Domestic Bank Account Details', type: 'FINANCIAL', regex: /(?:^|[^\p{L}\p{N}_])(בנק\s+\d{1,3}[,\s]+סניף\s+\d{1,3}[,\s]+(?:חשבון(?:\s+בנק|\s+מספר|\s+מס['’])?|מספר\s+חשבון)\s+\d{5,9})/giu },
73
102
  // 4. Israel Street Address (Hebrew street prefix with prepositions [במבל]?)
74
- { name: 'Israel Street Address', type: 'ADDRESS', isContextAddress: true, regex: /(?:^|[^\p{L}\p{N}_])((?:כתובת(?:\s*למשלוח)?[:\s#=-]+)?(?:[במבל]?(?:רחוב|רח['’]|שדרות|שד['’]|דרך|סמטת|סמטה|ת\.?ד\.?))\s+[\u0590-\u05FF'’"-]+(?:\s+[\u0590-\u05FF'’"-]+){0,3}\s+\d{1,4}(?:[ \t\xA0]*,?[ \t\xA0]*(?:דירה|כניסה|קומה)\s+\d{1,3})?(?:[ \t\xA0]*,?[ \t\xA0]*[\u0590-\u05FF'’"-]+(?:\s+[\u0590-\u05FF'’"-]+){0,2})?(?:[ \t\xA0]+(?:\d{7}|\d{5}))?)/giu },
103
+ { name: 'Israel Street Address', type: 'ADDRESS', isContextAddress: true, regex: /(?:^|[^\p{L}\p{N}_])(?:כתובת(?:\s*למשלוח|\s*מגורים)?[:\s#=-]+)?((?:[במבל]?(?:רחוב|רח['’]|שדרות|שד['’]|דרך|סמטת|סמטה|ת\.?ד\.?))\s+[\u0590-\u05FF'’"-]+(?:\s+[\u0590-\u05FF'’"-]+){0,3}\s+\d{1,4}(?:[ \t\xA0]*,?[ \t\xA0]*(?:דירה|כניסה|קומה)\s+\d{1,3})?(?:[ \t\xA0]*,?[ \t\xA0]*[\u0590-\u05FF'’"-]+(?:\s+[\u0590-\u05FF'’"-]+){0,2})?(?:[ \t\xA0]+(?:\d{7}|\d{5}))?)/giu },
75
104
  // 4b. Israel Tabu Cadastre (Block & Parcel / גוש וחלקה)
76
105
  { name: 'Israel Tabu Cadastre', type: 'ADDRESS', regex: /(?:^|[^\p{L}\p{N}_])((?:רישום(?:\\s+זכויות)?(?:\\s+בטאבו)?[:\\s#=-]*)?גוש\s+\d{3,6}\s+חלקה\s+\d{1,4}(?:\s+תת[- ]חלקה\s+\d{1,3})?)/giu },
77
106
  // 5a. Hebrew Professional Title Name (עו"ד, רו"ח, ד"ר, פרופ', כב' השופט, רשם, etc. with verb-guard)
@@ -79,9 +108,11 @@ let REGEX_RULES = [
79
108
  // 5b. Hebrew Contextual Role Name (תובע, נתבע, מבקש, משיב, נאמן, עובד, לקוח, etc.)
80
109
  { name: 'Hebrew Contextual Role Name', type: 'NAME', isContextName: true, regex: /(?:^|[^\p{L}\p{N}_])(?:שם\s+התובע|שם\s+התובעת|התובע|התובעת|שם\s+הנתבע|שם\s+הנתבעת|הנתבע|הנתבעת|המבקש|המבקשת|המשיב|המשיבה|הנאמן|הנאמנת|המערער|המערערת|החייב|החייבת|הזוכה|העובד|העובדת|עובד|עובדת|המעסיק|המעסיקה|מעסיק|המבוטח|המבוטחת|מבוטח|החולה|המטופל|המטופלת|הלקוח|הלקוחה|איש\s+קשר|הרוכש|הרוכשת|המוכר|המוכרת|השוכר|השוכרת|המשכיר|המשכירה|הערב|הערבה|מורשה\s+חתימה|שם|שם\s+מלא|מאת|לכבוד|השם\s+שלי|קוראים\s+לי)[ \t\xA0]*[:#=-][ \t\xA0]*((?!(?:(?:[ובלמ]?(?:עו"ד|עוה"ד|רו"ח|ד"ר|פרופ'|עורך\s+דין|עורכת\s+דין|רואה\s+חשבון|רואת\s+חשבון|השופט|השופטת|כב'[\s\xA0]+השופט|כב'[\s\xA0]+השופטת|רשם|רשמת|יועץ\s+מס|יועצת\s+מס|מר|גב'|גברת))|הגיש|הגישה|הגישו|אישר|אישרה|אישרו|חתם|חתמה|חתמו|הודיע|הודיעה|הודיעו|טוען|טוענת|טוענים|מייצג|מייצגת|מייצגים|פנה|פנתה|פנו|קבע|קבעה|קבעו|כתב|כתבה|כתבו|נכח|נכחה|נכחו|הורה|הורתה|הורו|סבור|סבורה|סבורים|מבקש|מבקשת|מבקשים|דורש|דורשת|דורשים|העיד|העידה|העידו|הסביר|הסבירה|הסבירו|נפטר|נפטרה|נפטרו|נולד|נולדה|נולדו|הופיע|הופיעה|הופיעו|בדק|בדקה|בדקו|טיפל|טיפלה|טיפלו|רשם|רשמה|רשמו|מסר|מסרה|מסרו|אמר|אמרה|אמרו|אתמול|היום|מחר|ביום|במשרד|בבית|בפני|בעניין|בע"מ|לפני|נגד|בדבר|מטעם)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?(?:[ \t\xA0]+(?:בן|בר|בת)[ \t\xA0]+\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?|[ \t\xA0]+(?!(?:(?:[ובלמ]?(?:עו"ד|עוה"ד|רו"ח|ד"ר|פרופ'|עורך\s+דין|עורכת\s+דין|רואה\s+חשבון|רואת\s+חשבון|השופט|השופטת|כב'[\s\xA0]+השופט|כב'[\s\xA0]+השופטת|רשם|רשמת|יועץ\s+מס|יועצת\s+מס|מר|גב'|גברת))|הגיש|הגישה|הגישו|אישר|אישרה|אישרו|חתם|חתמה|חתמו|הודיע|הודיעה|הודיעו|טוען|טוענת|טוענים|מייצג|מייצגת|מייצגים|פנה|פנתה|פנו|קבע|קבעה|קבעו|כתב|כתבה|כתבו|נכח|נכחה|נכחו|הורה|הורתה|הורו|סבור|סבורה|סבורים|מבקש|מבקשת|מבקשים|דורש|דורשת|דורשים|העיד|העידה|העידו|הסביר|הסבירה|הסבירו|נפטר|נפטרה|נפטרו|נולד|נולדה|נולדו|הופיע|הופיעה|הופיעו|בדק|בדקה|בדקו|טיפל|טיפלה|טיפלו|רשם|רשמה|רשמו|מסר|מסרה|מסרו|אמר|אמרה|אמרו|אתמול|היום|מחר|ביום|במשרד|בבית|בפני|בעניין|בע"מ|לפני|נגד|בדבר|מטעם)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?){1,2})(?=[ \t\xA0.,;:!?]|$|[^\p{L}\p{N}])/gu },
81
110
  // 5c. Hebrew Inverted Tabu / Registry Format (כהן, אברהם)
82
- { name: 'Hebrew Inverted Tabu Name', type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?!(?:שם|ח\.פ\.?|ת\.ז\.?|כתובת|גוש|חלקה|דירה|קומה|רחוב|עיר|מספר)[,\s])((\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?),[ \t\xA0]+(?!(?:הגיש|הגישה|הגישו|אישר|אישרה|אישרו|חתם|חתמה|חתמו|הודיע|הודיעה|הודיעו|טוען|טוענת|טוענים|מייצג|מייצגת|מייצגים|פנה|פנתה|פנו|קבע|קבעה|קבעו|כתב|כתבה|כתבו|נכח|נכחה|נכחו|הורה|הורתה|הורו|סבור|סבורה|סבורים|מבקש|מבקשת|מבקשים|דורש|דורשת|דורשים|העיד|העידה|העידו|הסביר|הסבירה|הסבירו|נפטר|נפטרה|נפטרו|נולד|נולדה|נולדו|הופיע|הופיעה|הופיעו|בדק|בדקה|בדקו|טיפל|טיפלה|טיפלו|רשם|רשמה|רשמו|מסר|מסרה|מסרו|אמר|אמרה|אמרו|אתמול|היום|מחר|ביום|במשרד|בבית|בפני|בעניין|בע"מ|לפני|נגד|בדבר|מטעם)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?)(?=[ \t\xA0.,;:!?]|$|[^\p{L}\p{N}])/gu },
83
- // 6. Pan-European / Global IBAN (with ISO 7064 Mod 97 validator)
84
- { name: 'International Bank Account Number (IBAN)', type: 'FINANCIAL', validatorType: 'mod97_iban', regex: /\b[A-Z]{2}\d{2}(?:[ \t]?[A-Z0-9]{4}){2,7}(?:[ \t]?[A-Z0-9]{1,4})?\b/g },
111
+ { name: 'Hebrew Inverted Tabu Name', type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?!(?:שם|ח\.פ\.?|ת\.ז\.?|כתובת|גוש|חלקה|דירה|קומה|רחוב|עיר|מספר|שלום|היי|אהלן|בוקר|ערב|לילה|תודה|בבקשה|סליחה|אני|אנחנו|אתה|את|הוא|היא|הם|הן|זה|זאת|כן|לא|אבל|או|אם|כי|לכן|אולם|כלומר|למשל|שמי)[,\s])((\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?),[ \t\xA0]+(?!(?:אני|אנחנו|אתה|את|הוא|היא|הם|הן|זה|זאת|שלום|תודה|כאן|שם|שמי|הגיש|הגישה|הגישו|אישר|אישרה|אישרו|חתם|חתמה|חתמו|הודיע|הודיעה|הודיעו|טוען|טוענת|טוענים|מייצג|מייצגת|מייצגים|פנה|פנתה|פנו|קבע|קבעה|קבעו|כתב|כתבה|כתבו|נכח|נכחה|נכחו|הורה|הורתה|הורו|סבור|סבורה|סבורים|מבקש|מבקשת|מבקשים|דורש|דורשת|דורשים|העיד|העידה|העידו|הסביר|הסבירה|הסבירו|נפטר|נפטרה|נפטרו|נולד|נולדה|נולדו|הופיע|הופיעה|הופיעו|בדק|בדקה|בדקו|טיפל|טיפלה|טיפלו|רשם|רשמה|רשמו|מסר|מסרה|מסרו|אמר|אמרה|אמרו|אתמול|היום|מחר|ביום|במשרד|בבית|בפני|בעניין|בע"מ|לפני|נגד|בדבר|מטעם)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}+(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?)(?=[ \t\xA0.,;:!?]|$|[^\p{L}\p{N}])/gu },
112
+ // 5d. Hebrew Conversational Intro Name (אני דוד כהן, שמי דוד כהן, קוראים לי דוד כהן)
113
+ { name: 'Hebrew Conversational Intro Name', type: 'NAME', isContextName: true, regex: /(?:^|[^\p{L}\p{N}_])(?:שמי|השם\s+שלי(?:\s+הוא)?|קוראים\s+לי|אני)[ \t\xA0]*[:#=-]?[ \t\xA0]+((?!(?:גר|גרה|גרים|עובד|עובדת|עובדים|רוצה|רוצים|צריך|צריכה|מבקש|מבקשת|כותב|כותבת|מדבר|מדברת|פונה|שואל|שואלת|חושב|חושבת|יודע|יודעת|יכול|יכולה|נמצא|נמצאת|מגיע|מגיעה|מתל|מירושלים|מחיפה|מבאר|מרמת|מפתח|מראשון|מנתניה|מאשדוד|מחולון|מבני|מכפר|מהרצליה|מרעננה|ממודיעין)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}{2,}(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?[ \t\xA0]+(?!(?:מתל|מירושלים|מחיפה|מבאר|מרמת|מפתח|מראשון|מנתניה|מאשדוד|מחולון|מבני|מכפר|מהרצליה|מרעננה|ממודיעין|בעיר|ברחוב|במשרד|בבית|בחברת|ומספר|ותעודת|טלפון|נייד|מייל|אימייל)[\s\xA0.,;:!?]|$)\p{sc=Hebrew}{2,}(?:['’]\p{sc=Hebrew}+)?(?:-\p{sc=Hebrew}+)?)(?=[ \t\xA0.,;:!?]|$|[^\p{L}\p{N}])/gu },
114
+ // 6. Pan-European / Global IBAN (with ISO 7064 Mod 97 validator, case-insensitive)
115
+ { name: 'International Bank Account Number (IBAN)', type: 'FINANCIAL', validatorType: 'mod97_iban', regex: /\b[A-Z]{2}\d{2}(?:[ \t]?[A-Z0-9]{4}){2,7}(?:[ \t]?[A-Z0-9]{1,4})?\b/gi },
85
116
  // 6b. SWIFT / BIC Code
86
117
  { name: 'SWIFT / BIC Code', type: 'FINANCIAL', regex: /\b(?:SWIFT|BIC|SWIFT\/BIC|SWIFT-BIC)[:\s#=-]*([A-Z]{4}[A-Z]{2}[A-Z0-9]{2}(?:[A-Z0-9]{3})?)\b/gi },
87
118
  // 7. European VAT / Tax ID (EU / UK / Pan-European VAT with optional spacing)
@@ -91,7 +122,7 @@ let REGEX_RULES = [
91
122
  // 7a2. Netherlands KvK Number (Chamber of Commerce / Handelsregister 8 digits)
92
123
  { name: 'Netherlands KvK Number', type: 'ID', regex: /\b(?:KvK(?:[- ]?nummer)?|Kamer\s+van\s+Koophandel)[:\s#=-]*(\d{8})\b/gi },
93
124
  // 7b. Netherlands Burgerservicenummer (BSN with 11-proef validator and test range)
94
- { name: 'Netherlands National ID (BSN)', type: 'ID', validatorType: 'mod11_bsn', regex: /(?:(?:\b(?:BSN(?:[- ]?nummers?)?|Burgerservicenummer(?:s)?|SOFI(?:[- ]?nummers?)?)[^\S\r\n]*(?:[:#=-]|\bis\b|\bzijn\b)?[^\S\r\n]*)|(?<!\d))(\d{8,9})\b(?!\d)/gi },
125
+ { name: 'Netherlands National ID (BSN)', type: 'ID', validatorType: 'mod11_bsn', regex: /(?:(?:\b(?:BSN(?:[- ]?nummers?)?|Burgerservicenummer(?:s)?|SOFI(?:[- ]?nummers?)?)[^\S\r\n]*(?:[:#=-]|\bis\b|\bzijn\b)?[^\S\r\n]*)|(?<![\d-]))(\d{8,9})\b(?![\d-])/gi },
95
126
  // 7c. Germany Tax ID (Steuer-ID / IdNr with ISO 7064 Mod 11, 10 validator)
96
127
  { name: 'Germany Tax ID (Steuer-ID / IdNr)', type: 'ID', validatorType: 'mod11_10_de_steuerid', regex: /(?:(?:\b(?:Steuer-?ID|IdNr|Steuernummer)[^\S\r\n]*(?:[:#=-]|\bist\b)?[^\S\r\n]*)|(?<!\d))([1-9]\d{10})\b(?!\d)/gi },
97
128
  // 7d. Germany Local Tax Number (Steuernummer)
@@ -112,6 +143,8 @@ let REGEX_RULES = [
112
143
  { name: 'UK Postcode', type: 'ADDRESS', regex: /\b[A-Z]{1,2}\d[A-Z\d]?[ \t\xA0]*\d[A-Z]{2}\b/gi },
113
144
  // 9b. Netherlands Postal Code
114
145
  { name: 'Netherlands Postal Code', type: 'ADDRESS', regex: /\b[1-9]\d{3}[ \t\xA0]*(?!SA|SD|SS)[A-Z]{2}\b/g },
146
+ // 9c. Brazil Postal Code (CEP)
147
+ { name: 'Brazil Postal Code (CEP)', type: 'ADDRESS', regex: /\b(?:CEP[^\S\r\n]*[:#=-]?[^\S\r\n]*\d{5}-?\d{3}|\d{5}-\d{3})\b/gi },
115
148
  // 10. UK Bank Sort Code
116
149
  { name: 'UK Bank Sort Code', type: 'FINANCIAL', regex: /(?:(?:\b(?:Sort\s+Code|SC)[:\s#=-]*)|(?<![\d/-]))(\d{2}[- ]\d{2}[- ]\d{2})\b(?!\d|:\d{2})/gi },
117
150
  // 10b. UK Bank Account Number (8 digits with explicit Account label)
@@ -121,27 +154,61 @@ let REGEX_RULES = [
121
154
  // 12. Canada Postal Code
122
155
  { name: 'Canada Postal Code', type: 'ADDRESS', regex: /\b[A-CEGHJ-NPR-TV-Z]\d[A-CEGHJ-NPR-TV-Z][ \t\xA0]*\d[A-CEGHJ-NPR-TV-Z]\d\b/gi },
123
156
  // 13. Australia Tax File Number (TFN with Mod 11 validator)
124
- { name: 'Australia Tax File Number (TFN)', type: 'ID', validatorType: 'mod11_tfn', regex: /(?:(?:\bTFN(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)|\b(?<!\d))(\d{3}[- ]?\d{3}[- ]?\d{2,3})\b(?!\d)/gi },
157
+ { name: 'Australia Tax File Number (TFN)', type: 'ID', validatorType: 'mod11_tfn', regex: /(?:(?:\bTFN(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)|\b(?<![\d-]))(\d{3}[- ]?\d{3}[- ]?\d{2,3})\b(?![\d-])/gi },
125
158
  // 14. Australia Phone Number
126
159
  { name: 'Australia Phone Number', type: 'PHONE', regex: /(?<!\w)(?:(?:\+?61[-.\s]?|0)(?:4\d{2}[-.\s]?\d{3}[-.\s]?\d{3}|[2378][-.\s]?\d{4}[-.\s]?\d{4}))\b/g },
127
160
  // 15. Australia Medicare Card
128
161
  { name: 'Australia Medicare Card', type: 'ID', regex: /(?:\bMedicare(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)\b([2-6]\d{9}(?:[ -]?\d)?)\b/gi },
129
162
  // 16. US Passport Number
130
163
  { name: 'US Passport Number', type: 'ID', regex: /(?:\b(?:Passport(?:\s*(?:No\.?|Number|#))?|U\.?S\.?\s+Passport)[:\s#=-]*)\b([A-Z0-9]{9})\b/gi },
131
-
132
- // Emails
164
+ // 17. India DPDP National Identifiers (PAN, GSTIN, Aadhaar)
165
+ { name: 'India Permanent Account Number (PAN)', type: 'ID', regex: /(?:(?:\bPAN(?:\s*(?:Card|No\.?|Number|#))?[:\s#=-]*)|(?<![A-Za-z0-9_-]))([A-Z]{3}[ABCFGHLJPT][A-Z]\d{4}[A-Z])\b(?![A-Za-z0-9_-])/gi },
166
+ { name: 'India GSTIN', type: 'ID', regex: /\b(?:0[1-9]|[12]\d|3[0-8])[A-Z]{3}[ABCFGHLJPT][A-Z]\d{4}[A-Z][1-9A-Z]Z[0-9A-Z]\b/g },
167
+ { name: 'India Aadhaar Number', type: 'ID', validatorType: 'aadhaar_in', regex: /(?:\b(?:Aadhaar|UIDAI)(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)([2-9]\d{3}[ -]?\d{4}[ -]?\d{4})\b/gi },
168
+ // 18. Poland National IDs (PESEL with Mod 10 & NIP with Mod 11)
169
+ { name: 'Poland National ID (PESEL)', type: 'ID', validatorType: 'mod10_pl_pesel', regex: /\bPESEL(?:\s*(?:nr\.?|no\.?|number|#))?[:\s#=-]*(\d{11})\b/gi },
170
+ { name: 'Poland Tax ID (NIP)', type: 'ID', validatorType: 'mod11_pl_nip', regex: /\bNIP(?:\s*(?:nr\.?|no\.?|number|#))?[:\s#=-]*(\d{3}-\d{3}-\d{2}-\d{2}|\d{3}-\d{2}-\d{2}-\d{3}|\d{10})\b/gi },
171
+ // 19. LATAM National IDs (Mexico CURP & RFC, Argentina CUIT/CUIL, Chile RUT)
172
+ { name: 'Mexico CURP', type: 'ID', regex: /(?:(?:\bCURP[:\s#=-]*)|(?<![A-Za-z0-9_-]))([A-Z]{4}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[HM][A-Z]{5}[0-9A-Z]\d)\b(?![A-Za-z0-9_-])/gi },
173
+ { name: 'Mexico RFC', type: 'ID', regex: /\bRFC(?:\s*(?:n[º°o]\.?|#))?[:\s#=-]*([A-Z&Ñ]{3,4}\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])[A-Z0-9]{3})\b/gi },
174
+ { name: 'Argentina CUIT / CUIL', type: 'ID', validatorType: 'mod11_ar_cuit', regex: /(?:(?:\bCUI[TL](?:\s*(?:n[º°o]\.?|#))?[:\s#=-]*)|(?<![\d-]))((?:20|23|24|27|30|33|34)-\d{8}-\d)\b(?![\d-])/gi },
175
+ { name: 'Chile RUT / RUN', type: 'ID', regex: /\bRU[TN](?:\s*(?:n[º°o]\.?|#))?[:\s#=-]*(\d{1,2}\.\d{3}\.\d{3}-[\dkK]|\d{7,8}-[\dkK])\b/gi },
176
+ // 20. APAC National IDs (Singapore NRIC/FIN, Japan My Number, South Korea RRN, China 18-digit Resident ID, Taiwan National ID, Hong Kong HKID)
177
+ { name: 'Singapore NRIC / FIN', type: 'ID', regex: /(?:(?:\b(?:NRIC|FIN)(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)|(?<![A-Za-z0-9_-]))([STFGM]\d{7}[A-Z])\b(?![A-Za-z0-9_-])/gi },
178
+ { name: 'Japan My Number', type: 'ID', regex: /\bMy\s*Number(?:\s*(?:Card|No\.?|Number|#))?[:\s#=-]*(\d{4}[ -]?\d{4}[ -]?\d{4})\b/gi },
179
+ { name: 'South Korea Resident Registration Number (RRN)', type: 'ID', validatorType: 'mod11_kr_rrn', regex: /(?:(?:\b(?:RRN|Jumin(?:\s*Bunho)?)(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)|(?<![\d-]))(\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])-[1-8]\d{6})\b(?![\d-])/gi },
180
+ { name: 'China Resident Identity Card (Shenfenzheng)', type: 'ID', validatorType: 'mod11_2_cn_id', regex: /(?:(?:\b(?:Shenfenzheng|CN\s+ID|Resident\s+ID|身份证(?:号|号码)?)[:\s#=-]*)|(?<![A-Za-z0-9_-]))([1-8]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx])\b(?![A-Za-z0-9_-])/giu },
181
+ { name: 'Taiwan National ID', type: 'ID', validatorType: 'mod10_tw_id', regex: /(?:(?:\b(?:TW\s+ID|Taiwan\s+ID|ROC\s+ID|身分證(?:字號)?)[:\s#=-]*)|(?<![A-Za-z0-9_-]))([A-Z][1289]\d{8})\b(?![A-Za-z0-9_-])/giu },
182
+ { name: 'Hong Kong Identity Card (HKID)', type: 'ID', regex: /(?:(?:\bHKID(?:\s*(?:No\.?|Number|#))?[:\s#=-]*)|(?<![A-Za-z0-9_-]))([A-Z]{1,2}\d{6}\([0-9A]\))(?![A-Za-z0-9_-])/gi },
183
+ // 21. Nordic, Swiss, Belgian, Austrian, Portuguese & UAE National IDs
184
+ { name: 'Sweden Personnummer', type: 'ID', validatorType: 'luhn_se_personnummer', regex: /(?:(?:\b(?:Personnummer|Samordningsnummer|Pnr)[:\s#=-]*)|(?<![\d-]))((?:19|20)?\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01]|[6-9]\d)[-+]\d{4})\b(?![\d-])/gi },
185
+ { name: 'Nordic National ID (CPR / Fødselsnummer / HETU)', type: 'ID', regex: /\b(?:CPR(?:-?nummer)?|Fødselsnummer|Foedselsnummer|Henkilötunnus|HETU)[:\s#=-]*(\d{6}[-+A]?\d{3}[\dA-Z]|\d{11})\b/gi },
186
+ { name: 'Switzerland AHV / AVS Number', type: 'ID', validatorType: 'ean13_ch_ahv', regex: /(?:(?:\b(?:AHV|AVS|OASI)(?:\s*(?:Nr\.?|No\.?|Number|#))?[:\s#=-]*)|(?<![\d.]))(756[.\s-]\d{4}[.\s-]\d{4}[.\s-]\d{2})\b(?![\d.])/gi },
187
+ { name: 'Belgium National Register Number (NISS)', type: 'ID', regex: /(?:(?:\b(?:Rijksregisternummer|NISS|INSZ|RN)[:\s#=-]*)|(?<![\d.-]))(\d{2}\.\d{2}\.\d{2}-\d{3}\.\d{2})\b(?![\d.-])/gi },
188
+ { name: 'Austria Sozialversicherungsnummer (SVN)', type: 'ID', regex: /\b(?:SVNR?|Sozialversicherungsnummer|VSNR)[:\s#=-]*(\d{4}[ \t-]?\d{2}(?:0[1-9]|1[0-2])\d{2})\b/gi },
189
+ { name: 'Portugal NIF (Contribuinte)', type: 'ID', validatorType: 'mod11_pt_nif', regex: /\b(?:PT\s+NIF|NIF|NIPC|Contribuinte)(?:\s*(?:n[º°o]\.?|#))?[:\s#=-]*([1235689]\d{8})\b/gi },
190
+ { name: 'UAE Emirates ID', type: 'ID', regex: /(?:(?:\bEmirates\s+ID[:\s#=-]*)|(?<![\d-]))(784-\d{4}-\d{7}-\d)\b(?![\d-])/gi },
191
+
192
+ // Emails (Standard, URL-Encoded %40, JSON Unicode-Escaped \u0040, HTML Entities &#64;/&#x40;, and Defanged IOCs)
133
193
  { type: 'EMAIL', regex: /\b[a-zA-Z0-9._%+-]{1,64}@[a-zA-Z0-9.-]{1,255}\.[a-zA-Z]{2,}\b/g },
134
-
135
- // Financial Data (Cards, Crypto, Direct Deposit & Bank Routing)
136
- { type: 'FINANCIAL', regex: /\b\d(?:[ -]?\d){12,18}\b/g },
194
+ { type: 'EMAIL', regex: /\b[a-zA-Z0-9._+-]{1,64}(?:%40|\\u0040|&#0*64;|&#x0*40;|&commat;)[a-zA-Z0-9.-]{1,255}\.[a-zA-Z]{2,}\b/gi },
195
+ { type: 'EMAIL', regex: /\b[a-zA-Z0-9._%+-]{1,64}\s*(?:\[at\]|\(at\)|@)\s*[a-zA-Z0-9.-]{1,255}(?:\s*(?:\[\.\]|\(\.\)|\[dot\]|\(dot\)|\.)\s*[a-zA-Z]{2,})+\b/gi },
196
+
197
+ // Financial Data (Cards, CVV/Exp, Crypto, Direct Deposit & Bank Routing)
198
+ { type: 'FINANCIAL', regex: /(?<![A-Za-z0-9/_-])\b\d(?:[ -]?\d){12,18}\b(?![A-Za-z0-9/_-])/g },
199
+ { type: 'FINANCIAL', regex: /\b(?:4\d{3}|5[1-5]\d{2}|222[1-9]|22[3-9]\d|2[3-6]\d{2}|27[01]\d|2720|6(?:011|5\d{2}))(?:\.\d{4}){3}\b/g },
200
+ { type: 'FINANCIAL', regex: /\b(?:CVV2?|CVC2?|CID|CSC|Security\s+Code|Card\s+Verification)\s*[:#=-]*\s*\d{3,4}\b/gi },
201
+ { type: 'FINANCIAL', regex: /\b(?:Exp(?:iry|iration|\.)?(?:\s+Date)?|Valid\s+Thru)\s*[:#=-]*\s*(?:0[1-9]|1[0-2])\s*[\/-]\s*(?:\d{2}|\d{4})\b/gi },
137
202
  { type: 'FINANCIAL', regex: /\b(?:1|3|bc1)[a-zA-HJ-NP-Z0-9]{25,39}\b/g },
138
203
  { type: 'FINANCIAL', regex: /\b0x[a-fA-F0-9]{40}\b/g },
204
+ { type: 'FINANCIAL', regex: /\bT(?=[1-9A-HJ-NP-Za-km-z]*\d)(?=[1-9A-HJ-NP-Za-km-z]*[A-Z])(?=[1-9A-HJ-NP-Za-km-z]*[a-z])[1-9A-HJ-NP-Za-km-z]{33}\b/g },
205
+ { type: 'FINANCIAL', regex: /\b(?:Solana|SOL)(?:\s+(?:Address|Wallet))?\s*[:=#-]+\s*([1-9A-HJ-NP-Za-km-z]{32,44})\b/gi },
139
206
  // Masked / Direct Deposit Bank Account Numbers & Routing Numbers
140
207
  { type: 'FINANCIAL', regex: /\b(?:Account|Acct|Checking|Savings|Direct\s+Deposit)\s*(?:#|ID|No\.?|Number)?[:\s#]*(?:[\*xX•.-]{3,}\d{2,6}|\d{4}[-\s]?\d{4}[-\s]?\d{2,6})\b/gi },
141
208
  { type: 'FINANCIAL', regex: /\b(?:ABA|Routing|RTN)\s*(?:#|ID|No\.?|Number)?[:\s#]*\d{9}\b/gi },
142
209
 
143
210
  // Professional IDs & Organizations
144
- { type: 'NAME', isContextName: true, isCorporateName: true, regex: /\b(?:[A-Z\xC0-\u024F][A-Za-z\xC0-\u024F0-9&.'-]*(?<!,)[ \t\xA0]+){1,5}(?:Inc\.?|LLC|Corp\.?|Corporation|Ltd\.?|Limited|Co\.?|Company|Group|Holdings|Solutions|Services|Servicing|Technologies|Logistics|Industries|Capital|Bank|Partners|LLP|PLLC|GmbH(?:\s+&\s+Co\.?\s*KG)?|AG|OHG|KG|UG(?:\s*\(haftungsbeschr[äa]nkt\))?|e\.V\.|Sp\.\s*z\s*o\.o\.|Automotive|Maschinenbau|Anlagenbau|Werkzeugbau|Feintechnik|Elektrotechnik)(?:\s+(?:LLC|Inc\.?|Corp\.?|Ltd\.?|USA|Group))?(?=\b|[.,;:\s]|$)/gu },
211
+ { type: 'NAME', isContextName: true, isCorporateName: true, regex: /\b(?:[A-Z\xC0-\u024F][A-Za-z\xC0-\u024F0-9&.'-]*(?<!,)[ \t\xA0]+){1,5}(?:Inc\.?|LLC|Corp\.?|Corporation|Ltd\.?|Ltda\.?|Limited|Co\.?|Company|Group|Holdings|Solutions|Services|Servicing|Technologies|Logistics|Industries|Capital|Bank|Partners|LLP|PLLC|S\/A|S\.A\.|EIRELI|GmbH(?:\s+&\s+Co\.?\s*KG)?|AG|OHG|KG|UG(?:\s*\(haftungsbeschr[äa]nkt\))?|e\.V\.|Sp\.\s*z\s*o\.o\.|Automotive|Maschinenbau|Anlagenbau|Werkzeugbau|Feintechnik|Elektrotechnik)(?:\s+(?:LLC|Inc\.?|Corp\.?|Ltd\.?|Ltda\.?|USA|Group))?(?=\b|[.,;:\s]|$)/gu },
145
212
  { type: 'ID', regex: /\b(?:Employee|Emp|EE|Worker|Staff|File|Badge|Member|Advisor|Producer|Agent|Borrower)\s*(?:#|ID|No\.?|Number)[:\s#]*([A-Z0-9-]{3,15})\b/gi },
146
213
  { type: 'ID', regex: /\b(?:Pay\s+Group[:#\s]+[A-Za-z0-9_-]{2,30}|(?:Cost\s+Center|Dept|Department)[:#\s]+[A-Za-z0-9_-]*\d[A-Za-z0-9_-]*)\b/gi },
147
214
  { type: 'ID', regex: /(?:\b(?:Box\s+d\b|d\.\s*(?:Control|#|number|no\.?|num)|d\s+Control)\s*(?:Control\s+)?(?:number|no\.?|#|num)?[:\s#]+|\bControl\s*(?:number|no\.?|#|num)?[:\s#]+|\bControl[:#]\s*)([A-Za-z0-9-]{3,30})/gi },
@@ -155,15 +222,15 @@ let REGEX_RULES = [
155
222
  { type: 'ADDRESS', regex: /\b(?:P\.?O\.?[ \t\xA0]*Box|PO[ \t\xA0]*Box|Postbus|Postfach|(?:BP|B\.P\.)(?![ \t\xA0]*\d{2,3}\s*\/\s*\d{2,3}(?:[ \t\xA0]*(?:mmHg|mm\s*Hg))?)|Bo[îi]te[ \t\xA0]+Postale|Apartado(?:[ \t\xA0]+de[ \t\xA0]+Correos)?|Apdo\.|Casella[ \t\xA0]+Postale)[ \t\xA0]+(?:[A-Z0-9-]{1,8}(?:[ \t\xA0]+[A-Z0-9-]{1,8}){0,3}|\d{1,6})\b(?:[ \t\xA0]*,?[ \t\xA0]*(?:(?:[1-9]\d{3}[ \t\xA0]*(?!SA|SD|SS)[A-Z]{2}|\d{4,5})[ \t\xA0]+[\p{Lu}][\p{L}'’-]+))?/giu },
156
223
  // European Street Addresses (Dutch / Netherlands: Canals, Dijk, Kade, Straat, Laan, Plein)
157
224
  { type: 'ADDRESS', regex: /\b(?:(?:[A-Z\p{Lu}][\p{L}'’-]*(?:gracht|kade|singel|dijk|weg|straat|laan|plein|steeg|dreef|burgwal|dam|rak)|Damrak|Coolsingel|Rokin|Spui|(?:[A-Z\p{Lu}][\p{L}'’-]+[ \t\xA0]+)+(?:gracht|kade|singel|dijk|weg|straat|laan|plein|steeg|dreef|burgwal|dam|rak))\b[ \t\xA0]+\d{1,4}[a-zA-Z]?(?:[ \t\xA0]*,?[ \t\xA0]*(?:(?:[1-9]\d{3}[ \t\xA0]*(?!SA|SD|SS)[A-Z]{2})?[ \t\xA0]+[\p{Lu}][\p{L}'’-]+))?)/giu },
158
- // European Street Addresses (Romance: France, Spain, Italy, Portugal, etc.)
159
- { type: 'ADDRESS', regex: /(?:(?:\b[1-9]\d{0,3}[a-zA-Z]?[, \t\xA0]+\b(?:Rue|(?:(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)R\.(?!\s+(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b))|Avenue|Av\.|Avda\.|Boulevard|Blvd|Bd|Allée|Allee|Chemin|Quai|Impasse|Sq\.|Rte|Calle|C\/|Paseo|P\.º|Rambla|Plaza|Plza\.|Ronda|Travesía|Travesia|(?:(?<!\b(?:sent|executed|shipped|delivered|via|routed|available|transferred|transfer|transfers|convey|conveys|conveyed|grant|granted|grants|accessible|authenticated|verified|signed|recorded|record|evaluated|administered|assessed|measured|determined|reported|tested|analyzed|handled|managed|operated|provided|arranged|booked|conducted|performed|payout|pay|paid|authorized|process|processed|received|issued|remitted|dispatch|dispatched)\s+)Via(?!\s+(?:DOCUSIGN|TRANSCRIPT|EMAIL|API|PORTAL|FEED|SERVICE|WEBHOOK|SMS|HTTP|HTTPS|FTP|TCP|IP|VPN|SSH|APP|LINK|AUTH|METHOD|ID|NUMBER|NO|REF|CODE|Federal(?:\s+Reserve)?|Fedwire|SWIFT|ACH|RTGS|CHIPS|SEPA|BACS|Target2|Bank|Wire|Grant(?:\s+Deed)?|Warranty(?:\s+Deed)?|Quitclaim(?:\s+Deed)?|Deed|CTCAE|MedDRA|WHO|FDA|EMA|ICH|GCP|IV|SC|Oral|IBAN|ACCOUNT|ROUTING|WIRE|TRANSFER|CARD|PAYMENT|PAYOUT|DEBIT|CREDIT|CRYPTO|PAYPAL|TOKEN|KEY|DEPLOY|SSH|SECRET|CHECK|CHEQUE|INVOICE|ORDER|SETTLEMENT)\b))|Viale|V\.le|Corso|C\.so|Piazza|P\.zza|Largo|Vicolo|Avenida|Praceta|Praça|Praca|Alameda|Travessa|Calçada|Calcada|(?:Place|Square|Route|Passage|Cours|Voie)(?=[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}]))[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*(?:[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*){0,4}(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+)?)|(?:\b(?:Rue|(?:(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)R\.(?!\s+(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b))|Avenue|Av\.|Avda\.|Boulevard|Blvd|Bd|Allée|Allee|Chemin|Quai|Impasse|Sq\.|Rte|Calle|C\/|Paseo|P\.º|Rambla|Plaza|Plza\.|Ronda|Travesía|Travesia|(?:(?<!\b(?:sent|executed|shipped|delivered|via|routed|available|transferred|transfer|transfers|convey|conveys|conveyed|grant|granted|grants|accessible|authenticated|verified|signed|recorded|record|evaluated|administered|assessed|measured|determined|reported|tested|analyzed|handled|managed|operated|provided|arranged|booked|conducted|performed|payout|pay|paid|authorized|process|processed|received|issued|remitted|dispatch|dispatched)\s+)Via(?!\s+(?:DOCUSIGN|TRANSCRIPT|EMAIL|API|PORTAL|FEED|SERVICE|WEBHOOK|SMS|HTTP|HTTPS|FTP|TCP|IP|VPN|SSH|APP|LINK|AUTH|METHOD|ID|NUMBER|NO|REF|CODE|Federal(?:\s+Reserve)?|Fedwire|SWIFT|ACH|RTGS|CHIPS|SEPA|BACS|Target2|Bank|Wire|Grant(?:\s+Deed)?|Warranty(?:\s+Deed)?|Quitclaim(?:\s+Deed)?|Deed|CTCAE|MedDRA|WHO|FDA|EMA|ICH|GCP|IV|SC|Oral|IBAN|ACCOUNT|ROUTING|WIRE|TRANSFER|CARD|PAYMENT|PAYOUT|DEBIT|CREDIT|CRYPTO|PAYPAL|TOKEN|KEY|DEPLOY|SSH|SECRET|CHECK|CHEQUE|INVOICE|ORDER|SETTLEMENT)\b))|Viale|V\.le|Corso|C\.so|Piazza|P\.zza|Largo|Vicolo|Avenida|Praceta|Praça|Praca|Alameda|Travessa|Calçada|Calcada|(?:Place|Square|Route|Passage|Cours|Voie)(?=[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}]))[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*(?:[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*){0,4}(?:(?:[, \t\xA0]+(?:nº|n°|no\.?|num\.?|#)?[ \t\xA0]*\b[1-9]\d{0,3}[a-zA-Z]?\b(?!\d)(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+)?)|(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+))))/giu },
225
+ // European & Brazilian/Romance Street Addresses (France, Spain, Italy, Portugal, Brazil: Rua, Avenida, Av., Alameda, Praça, Rodovia, etc.)
226
+ { type: 'ADDRESS', regex: /(?:(?:\b[1-9]\d{0,3}[a-zA-Z]?[, \t\xA0]+\b(?:Rue|Rua|Rodovia|Estrada|(?:(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)R\.(?!\s+(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b))|Avenue|Av\.|Avda\.|Boulevard|Blvd|Bd|Allée|Allee|Chemin|Quai|Impasse|Sq\.|Rte|Calle|C\/|Paseo|P\.º|Rambla|Plaza|Plza\.|Ronda|Travesía|Travesia|(?:(?<!\b(?:sent|executed|shipped|delivered|via|routed|available|transferred|transfer|transfers|convey|conveys|conveyed|grant|granted|grants|accessible|authenticated|verified|signed|recorded|record|evaluated|administered|assessed|measured|determined|reported|tested|analyzed|handled|managed|operated|provided|arranged|booked|conducted|performed|payout|pay|paid|authorized|process|processed|received|issued|remitted|dispatch|dispatched)\s+)Via(?!\s+(?:DOCUSIGN|TRANSCRIPT|EMAIL|API|PORTAL|FEED|SERVICE|WEBHOOK|SMS|HTTP|HTTPS|FTP|TCP|IP|VPN|SSH|APP|LINK|AUTH|METHOD|ID|NUMBER|NO|REF|CODE|Federal(?:\s+Reserve)?|Fedwire|SWIFT|ACH|RTGS|CHIPS|SEPA|BACS|Target2|Bank|Wire|Grant(?:\s+Deed)?|Warranty(?:\s+Deed)?|Quitclaim(?:\s+Deed)?|Deed|CTCAE|MedDRA|WHO|FDA|EMA|ICH|GCP|IV|SC|Oral|IBAN|ACCOUNT|ROUTING|WIRE|TRANSFER|CARD|PAYMENT|PAYOUT|DEBIT|CREDIT|CRYPTO|PAYPAL|TOKEN|KEY|DEPLOY|SSH|SECRET|CHECK|CHEQUE|INVOICE|ORDER|SETTLEMENT)\b))|Viale|V\.le|Corso|C\.so|Piazza|P\.zza|Largo|Vicolo|Avenida|Praceta|Praça|Praca|Alameda|Travessa|Calçada|Calcada|(?:Place|Square|Route|Passage|Cours|Voie)(?=[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}]))[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*(?:[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*){0,4}(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+)?)|(?:\b(?:Rue|Rua|Rodovia|Estrada|(?:(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)R\.(?!\s+(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b))|Avenue|Av\.|Avda\.|Boulevard|Blvd|Bd|Allée|Allee|Chemin|Quai|Impasse|Sq\.|Rte|Calle|C\/|Paseo|P\.º|Rambla|Plaza|Plza\.|Ronda|Travesía|Travesia|(?:(?<!\b(?:sent|executed|shipped|delivered|via|routed|available|transferred|transfer|transfers|convey|conveys|conveyed|grant|granted|grants|accessible|authenticated|verified|signed|recorded|record|evaluated|administered|assessed|measured|determined|reported|tested|analyzed|handled|managed|operated|provided|arranged|booked|conducted|performed|payout|pay|paid|authorized|process|processed|received|issued|remitted|dispatch|dispatched)\s+)Via(?!\s+(?:DOCUSIGN|TRANSCRIPT|EMAIL|API|PORTAL|FEED|SERVICE|WEBHOOK|SMS|HTTP|HTTPS|FTP|TCP|IP|VPN|SSH|APP|LINK|AUTH|METHOD|ID|NUMBER|NO|REF|CODE|Federal(?:\s+Reserve)?|Fedwire|SWIFT|ACH|RTGS|CHIPS|SEPA|BACS|Target2|Bank|Wire|Grant(?:\s+Deed)?|Warranty(?:\s+Deed)?|Quitclaim(?:\s+Deed)?|Deed|CTCAE|MedDRA|WHO|FDA|EMA|ICH|GCP|IV|SC|Oral|IBAN|ACCOUNT|ROUTING|WIRE|TRANSFER|CARD|PAYMENT|PAYOUT|DEBIT|CREDIT|CRYPTO|PAYPAL|TOKEN|KEY|DEPLOY|SSH|SECRET|CHECK|CHEQUE|INVOICE|ORDER|SETTLEMENT)\b))|Viale|V\.le|Corso|C\.so|Piazza|P\.zza|Largo|Vicolo|Avenida|Praceta|Praça|Praca|Alameda|Travessa|Calçada|Calcada|(?:Place|Square|Route|Passage|Cours|Voie)(?=[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}]))[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*(?:[ \t\xA0]+(?:(?:de[ \t\xA0]+la|de[ \t\xA0]+l'|de[ \t\xA0]+l’|de[ \t\xA0]+los|de[ \t\xA0]+las|de[ \t\xA0]+|des[ \t\xA0]+|du[ \t\xA0]+|del[ \t\xA0]+|dels[ \t\xA0]+|delle[ \t\xA0]+|degli[ \t\xA0]+|dei[ \t\xA0]+|d'|d’|da[ \t\xA0]+|do[ \t\xA0]+|dos[ \t\xA0]+|das[ \t\xA0]+)[ \t\xA0]*)?[\p{Lu}][\p{L}'’.-]*){0,4}(?:(?:[, \t\xA0]+(?:nº|n°|no\.?|num\.?|#)?[ \t\xA0]*\b[1-9]\d{0,4}[a-zA-Z]?\b(?!\d)(?:[ \t\xA0]*,?[ \t\xA0]*(?:Apto\.?|Ap\.?|Apartamento|Bloco|Sala|Conj\.?|Conjunto)[ \t\xA0]*[A-Za-z0-9-]+)?(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+)?)|(?:[, \t\xA0]+(?:(?:F-|D-|ES-|IT-|FR-|NL-|CH-|AT-|PT-)?\d{4,5}(?:-\d{3,4})?)[ \t\xA0]+[\p{Lu}][\p{L}'-]+))))/giu },
160
227
  // European Street Addresses (Germanic: Germany, Austria, Switzerland, etc.)
161
228
  { type: 'ADDRESS', regex: /\b(?:(?:Am|An\s+der|An\s+dem|Unter\s+den|Auf\s+dem|Auf\s+der|Beim|Zum|Vor\s+dem)[ \t\xA0]+[\p{Lu}][\p{L}.-]+(?:[ \t\xA0]+[\p{Lu}][\p{L}.-]+)?|(?:[\p{Lu}][\p{L}.-]+[ \t\xA0]+(?:Straße|Strasse|Weg|Gasse|Allee|Damm|Ring|Platz|Ufer|Chaussee|Pfad|Zeile|Markt|Tor|Str\.)|[\p{Lu}][\p{L}.-]*(?:straße|strasse|str\.|weg|gasse|allee|damm|ring|platz|ufer|chaussee|pfad|zeile|graben|markt|tor)))\b[ \t\xA0]+\b\d{1,4}[a-zA-Z]?\b(?!\d)(?:[ \t\xA0]*,?[ \t\xA0]*(?:Wohnung|Whg|App|Zimmer|Stock|Etage|OG|EG)\.?[ \t\xA0]*[A-Za-z0-9-]+)?(?:[, \t\xA0]+(?:D-|AT-|CH-)?\d{4,5}[ \t\xA0]+[\p{Lu}][\p{L}'-]+)?/giu },
162
229
  { type: 'ADDRESS', regex: /\b[A-Za-z][a-zA-Z\s.-]{1,25},?\s+(?:AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC|PR)\s+\d{5}(?:-\d{4})?\b/g },
163
230
  { type: 'ADDRESS', regex: /\b(?:ZIP|Postal|Code)?\s*(?:AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC|PR)\s+\d{5}(?:-\d{4})?\b/g },
164
231
  { type: 'ADDRESS', regex: /\b\d{5}-\d{4}\b/g },
165
- // SSN / National IDs
166
- { type: 'ID', regex: /\b\d{3}-\d{2}-\d{4}\b/g },
232
+ // SSN / National IDs (including JSON Unicode-escaped \u002d and URL-encoded %2D)
233
+ { type: 'ID', regex: /\b\d{3}(?:-|%2D|\\u002d)\d{2}(?:-|%2D|\\u002d)\d{4}\b/gi },
167
234
  { type: 'ID', regex: /(?<=^|[^\w*])(?:\*{3}|X{3}|x{3}|\*{2}|X{2}|x{2})[ -]?(?:\*{2}|X{2}|x{2})\b/gi },
168
235
  { type: 'ID', regex: /(?<=^|[^\w*])(?:\*{3}|X{3}|x{3}|\*{2}|X{2}|x{2})[ -]?(?:\*{2}|X{2}|x{2})[ -]?\d{4}\b/g },
169
236
  { type: 'ID', regex: /\b\d{2}-\d{7}\b/g },
@@ -172,11 +239,25 @@ let REGEX_RULES = [
172
239
  { type: 'LOCATION', regex: /\b[A-Za-z][a-zA-Z .'-]{1,25},\s*(?:AL|AK|AZ|AR|CA|CO|CT|DE|FL|GA|HI|ID|IL|IN|IA|KS|KY|LA|ME|MD|MA|MI|MN|MS|MO|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VT|VA|WA|WV|WI|WY|DC|PR)\b/g },
173
240
 
174
241
  // General Privacy, Dates & Secrets
175
- { type: 'DATE', regex: /\b(?:DOB|BIRTHDAY|Date of Birth)[\s:]+([0-9./-]{6,10})\b/gi },
242
+ { type: 'DATE', regex: /\b(?:DOB|BIRTHDAY|Date\s+of\s+Birth|Data\s+de\s+Nascimento|Fecha\s+de\s+Nacimiento|Date\s+de\s+naissance|Geburtsdatum|born(?:\s+on)?)[\s:#=-]+([0-9./-]{6,10}|(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember))\.?\s+\d{1,2}(?:st|nd|rd|th)?,?\s+\d{4}|\d{1,2}(?:st|nd|rd|th)?\s+(?:of\s+)?(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember))\.?,?\s+\d{4})\b/gi },
176
243
  { type: 'SECRET', regex: /\b(?:PASSWORD|PWD|SECRET)\s*[:=]\s*["']?[\S]{4,}["']?/gi },
244
+ { type: 'SECRET', regex: /(?:\bcurl\b[^\r\n]*?\s(?:-u|--user)\s+["']?[A-Za-z0-9_.+-]+:[^\s"']+|\b(?:--password|--api-key|--apikey|--access-token|--auth-token|--client-secret|--secret-key|--private-key)(?:=|\s+)["']?[^\s"']{6,}["']?)/gi },
245
+ { type: 'SECRET', regex: /\b(?:X-API-Key|X-Amz-Security-Token|X-Functions-Key|X-Shopify-Access-Token|X-Auth-Token|Api-Key)\s*:\s*["']?[A-Za-z0-9_.\-~+/=]{12,}["']?/gi },
246
+ { type: 'SECRET', regex: /(?:DefaultEndpointsProtocol=https?;AccountName=[^;\s"']+;AccountKey=[A-Za-z0-9+/=]{20,}(?:;EndpointSuffix=[^;\s"']+)?|Endpoint=sb:\/\/[^;\s"']+;SharedAccessKeyName=[^;\s"']+;SharedAccessKey=[A-Za-z0-9+/=]{16,}|\bSharedAccessSignature=sr=[^\s"']+&sig=[A-Za-z0-9%/+=]{16,}|\bAccountKey=[A-Za-z0-9+/=]{40,88}==)/gi },
247
+ { type: 'SECRET', regex: /\b(?:h(?:tt|xx)ps?|s?f(?:t|x)p|wss?):\/\/[a-zA-Z0-9_.%+-]+:[^\s/@:"']+@[a-zA-Z0-9_.-]+(?:(?:\[\.\]|\(\.\))[a-zA-Z0-9_.-]+)*(?::\d+)?(?:\/[^\s"']*)?/gi },
248
+ { type: 'SECRET', regex: /(?<=["']?(?:auth|identitytoken|registrytoken|\.dockerconfigjson)["']?\s*:\s*["'])[A-Za-z0-9+/=]{16,}(?=["'])/gi },
249
+ { type: 'SECRET', regex: /\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^\s"']+/gi },
250
+ { type: 'SECRET', regex: /(?<=\bAuthorization\s*:\s*Basic\s+)[A-Za-z0-9+/=]{8,}(?![A-Za-z0-9+/=])/gi },
251
+ { type: 'SECRET', regex: /(?<![A-Za-z0-9])(?:\$2[aby]\$\d{2}\$[./A-Za-z0-9=]{20,60}|\$argon2(?:id|i|d)\$[^\s"']{15,120})/g },
252
+ { type: 'SECRET', regex: /\bAIza[0-9A-Za-z_-]{35}\b/g },
253
+ { type: 'SECRET', regex: /\bsk-ant-(?:api\d{2}|admin\d{2}|sid\d{2})-[a-zA-Z0-9_-]{16,128}\b/g },
254
+ { type: 'SECRET', regex: /\bhttps:\/\/(?:hooks\.slack\.com\/services\/T[A-Z0-9_]+\/B[A-Z0-9_]+\/[A-Za-z0-9_-]+|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d{17,20}\/[A-Za-z0-9_-]{60,70})\b/gi },
255
+ { type: 'SECRET', regex: /\b\d{8,10}:AA[A-Za-z0-9_-]{33}\b/g },
256
+ { type: 'SECRET', regex: /\bAGE-SECRET-KEY-1[A-Z0-9]{58}\b/g },
257
+ { type: 'SECRET', regex: /\b(?:sbp_[a-fA-F0-9]{40}|dop_v1_[a-fA-F0-9]{64}|doo_v1_[a-fA-F0-9]{64}|dp\.(?:st|pt|ct|sa)\.[a-zA-Z0-9_-]{20,}|lin_api_[a-zA-Z0-9]{32,64}|ntn_[a-zA-Z0-9]{32,64}|secret_[a-zA-Z0-9]{32,64}|gsk_[a-zA-Z0-9]{32,64}|r8_[a-zA-Z0-9]{24,64}|tvly-[a-zA-Z0-9_-]{20,64}|fc-[a-fA-F0-9]{32}|dapi[a-fA-F0-9]{32}(?:-\d)?|re_[a-zA-Z0-9_]{20,64}|pypi-[A-Za-z0-9_-]{16,128}|whsec_[A-Za-z0-9+/=_-]{24,128}|shpat_[a-fA-F0-9]{32}|shpss_[a-fA-F0-9]{32}|shpca_[a-fA-F0-9]{32}|shppa_[a-fA-F0-9]{32}|hvs\.[A-Za-z0-9_-]{24,128}|hvb\.[A-Za-z0-9_-]{24,128}|glsa_[A-Za-z0-9]{32}_[A-Fa-f0-9]{8}|glc_[A-Za-z0-9+/=_-]{20,128}|pscale_tkn_[A-Za-z0-9_.-]{20,64}|pscale_pw_[A-Za-z0-9_.-]{20,64}|pat[A-Za-z0-9]{14}\.[a-fA-F0-9]{64}|pat-(?:na1|eu1)-[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}|sq0atp-[A-Za-z0-9_-]{22,64}|sq0csp-[A-Za-z0-9_-]{32,64}|NRAK-[A-Z0-9]{27}|v1\.0-[a-fA-F0-9]{24}-[a-fA-F0-9]{16,160}|ATATT3xFfGF0[A-Za-z0-9+/=_-]{24,128}|tskey-(?:auth|api|client)-[A-Za-z0-9_-]{20,96}|sntry[su]_[A-Za-z0-9+/=_-]{32,160}|(?:pk|sk)\.eyJ1[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{16,}|key-[a-f0-9]{32}|[a-f0-9]{32}-us\d{1,2}|napi_[a-zA-Z0-9]{32,64}|[a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9]{50,80})\b/g },
177
258
 
178
259
  // Standard IDs (SSN, EIN, Passport, VAT, European National IDs)
179
- { type: 'ID', regex: /\b\d{3}-\d{2}-\d{4}\b/g },
260
+ { type: 'ID', regex: /\b\d{3}(?:-|%2D|\\u002d)\d{2}(?:-|%2D|\\u002d)\d{4}\b/gi },
180
261
  { type: 'ID', regex: /\b(?:XXX|xxx|\*\*\*)[ -]?(?:XX|xx|\*\*)[ -]?\d{4}\b/g },
181
262
  { type: 'ID', regex: /\b\d{2}-\d{7}\b/g },
182
263
  { type: 'ID', regex: /(?:(?:[A-Za-z]:\\|\/(?:usr|var|etc|home|root|Users|private|tmp|opt|bin|sbin|dev|Applications|Library)\/)[a-zA-Z0-9_.-]+(?:[\/\\][a-zA-Z0-9_.-]+)*|\/(?:[a-zA-Z0-9_.-]+\/)+[a-zA-Z0-9_.-]+\.(?:txt|pdf|docx|xlsx|csv|js|ts|json|env|log|key|pem|crt|conf|yaml|yml|xml|html|sql|py|go|rs|c|cpp|h|sh|bin|zip|tar|gz|png|jpg|jpeg|svg|webp|wasm)\b)/g },
@@ -185,12 +266,15 @@ let REGEX_RULES = [
185
266
  // Netherlands Chamber of Commerce (KvK)
186
267
  { type: 'ID', regex: /\b(?:KvK|Kamer\s+van\s+Koophandel)[^\S\r\n]*[:#=-]?[^\S\r\n]*(\d{8})\b/gi },
187
268
  { type: 'ID', regex: /\b[A-Z]{2}[0-9]{6,12}\b/gi },
188
- { type: 'ID', regex: /[A-Z0-9<]{30,44}/g },
269
+ { type: 'ID', regex: /(?<![a-zA-Z0-9/_-])[A-Z0-9<]{30,44}(?![a-zA-Z0-9/_-])/g },
189
270
 
190
- // IT / Technical IDs
271
+ // IT / Technical IDs (IPv4-mapped IPv6, IPv4, Defanged IPv4, Full & Compressed IPv6, MAC Address, UUID)
272
+ { type: 'IP', regex: /(?:(?:\b(?:0{1,4}:){5}ffff:)|::(?:ffff:)?)(?:\d{1,3}\.){3}\d{1,3}\b/gi },
191
273
  { type: 'IP', regex: /(?<!\b(?:Chrome|Version|Firefox|Safari|Edge|Edg|OPR|Opera|Brave|V8|Node|Electron)\/)\b(?!(?:0\.0\.0\.0|127\.0\.0\.1)\b)(?:\d{1,3}\.){3}\d{1,3}\/(?:[0-9]|[12]\d|3[0-2])\b/g },
192
274
  { type: 'IP', regex: /(?<!\b(?:Chrome|Version|Firefox|Safari|Edge|Edg|OPR|Opera|Brave|V8|Node|Electron)\/)\b(?!(?:0\.0\.0\.0|127\.0\.0\.1)\b)(?:\d{1,3}\.){3}\d{1,3}\b/g },
193
- { type: 'IP', regex: /\b(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}\b/g },
275
+ { type: 'IP', regex: /\b\d{1,3}(?:\[\.\]|\(\.\)|\.)\d{1,3}(?:\[\.\]|\(\.\)|\.)\d{1,3}(?:\[\.\]|\(\.\))\d{1,3}\b/g },
276
+ { type: 'IP', regex: /\b(?:(?:[a-fA-F0-9]{1,4}:){7}[a-fA-F0-9]{1,4}|(?:[a-fA-F0-9]{1,4}:){1,7}:(?:[a-fA-F0-9]{1,4}(?::[a-fA-F0-9]{1,4}){0,6})?|::(?:[a-fA-F0-9]{1,4}(?::[a-fA-F0-9]{1,4}){0,6})?)(?:%[0-9a-zA-Z]{1,})?(?![:a-fA-F0-9])/g },
277
+ { type: 'ID', regex: /\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b|\b(?:[0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}\b/g },
194
278
  { type: 'ID', regex: /\b[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}\b/g },
195
279
  { type: 'ID', regex: /(?<!\b(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|CONNECT|TRACE)\s+)(?<![:/A-Za-z0-9])(?:\/[a-zA-Z0-9_.-]+(?:[\/\\][a-zA-Z0-9_.-]+)+|\b[a-zA-Z]:\\[\w.-]+(?:\\[\w.-]+)*)/g },
196
280
  { type: 'ID', regex: /\b\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::\d{2})?)?\b/g },
@@ -198,12 +282,12 @@ let REGEX_RULES = [
198
282
  { type: 'ID', regex: /(?<!\[)\b\d{2}\/\d{2}\/\d{4}\b(?![ \t\xA0]*(?:,\s*)?\d{1,2}:\d{2})/g },
199
283
 
200
284
  // Phone Numbers
201
- { type: 'PHONE', regex: /(?<!\b(?:DFARS|FAR|CFR)\s+)(?<!\w)(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?:\s*(?:ext\.?|x)\s*\d{1,5})?\b/gi },
285
+ { type: 'PHONE', regex: /(?<!\b(?:DFARS|FAR|CFR)\s+)(?<![\w.])(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?:\s*(?:ext\.?|x)\s*\d{1,5})?\b(?!\.\d)/gi },
202
286
  { type: 'PHONE', regex: /(?<!\w)(?:(?:Durchwahl|DW(?:[ \t]+ist)?|App\.|Kl\.)[:\s#=-]*\d{2,5}|intern[ \t]*[:#/-]+[ \t]*\d{2,5}|(?<=\b(?:Tel(?:efon)?|Phone|Fon|Durchwahl|DW|Rufnummer|unter)[:\s,]+(?:ist\s+)?(?:\d{2,5}[,\s]+)?)intern[ \t]*\d{2,5}|intern[ \t]*\d{2,5}(?=\s*(?:erreichbar|anrufen|wählen|durchrufen|kontaktieren))|\bintern(?:e)?\s+(?:Durchwahl|DW|Apparat|Rufnummer|Nummer|Nr\.?)[:\s#=-]*\d{2,5})\b/gi },
203
287
  { type: 'PHONE', regex: /\+\d{1,3}[ \t.-]+(?:\(?\d{1,4}\)?[ \t.-]+)?\d{2,8}(?:[ \t.-]+\d{2,8}){0,4}\b/g },
204
288
  { type: 'PHONE', regex: /\+?[1-9]\d{1,3}[\s.-]\(?\d{1,4}\)?[\s.-]\d{2,4}[\s.-]\d{4}/g },
205
289
  { type: 'PHONE', regex: /(?:\+44\s?7\d{3}|\(?07\d{3}\)?)\s?\d{3}\s?\d{3}\b/g },
206
- { type: 'PHONE', regex: /(?<!\b(?:DFARS|FAR|CFR)\s+)(?<![A-Za-z0-9.-])\b(?:\d{3}[-.\s]\d{4}|\(\d{3}\)\s??\d{3}[-.\s]??\d{4}|\d{3}[-.\s]??\d{3}[-.\s]??\d{4})\b(?![A-Za-z0-9-])/g },
290
+ { type: 'PHONE', regex: /(?<!\b(?:DFARS|FAR|CFR)\s+)(?<![A-Za-z0-9.-])\b(?:\d{3}[-.\s]\d{4}|\(\d{3}\)\s??\d{3}[-.\s]??\d{4}|\d{3}[-.\s]??\d{3}[-.\s]??\d{4})\b(?![A-Za-z0-9-]|\.\d)/g },
207
291
 
208
292
  // Geolocation (Lat/Long)
209
293
  { type: 'LOCATION', regex: /\b-?\d{1,3}\.\d{4,6}[° ]?[NSns],\s*-?\d{1,3}\.\d{4,6}[° ]?[EWew]\b/g },
@@ -214,23 +298,23 @@ let REGEX_RULES = [
214
298
  { type: 'ID', regex: /\bDRIVER[S]?\s+LICENSE[ -]?\d{6,15}\b/gi },
215
299
 
216
300
  // Names with Honorifics (International & European: Dr., Prof., Mr., Sir, Lord, Herr, Frau, Me, Avv., Dott.ssa, Don, Dhr., etc.)
217
- { name: 'Names with Honorifics', type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?:Kollege|Kollegin|Instandhalter|Instandhalterin|Disponent|Disponentin|Werkstudent|Werkstudentin|Lagerleiter|Lagerleiterin|Arbeitsvorbereiter|Arbeitsvorbereiterin|Qualitätsprüfer|Qualitätsprüferin|Dott\.ssa|Sig\.ra|Mr|Mrs|Ms|Miss|Dr|Prof|Hon|Rev|Judge|Atty|Doctor|Professor|Sir|Lord|Lady|Dame|Herr|Frau|Rechtsanwalt|Rechtsanwältin|Notar|Schichtleiter|Schichtleiterin|Schichtführer|Schichtführerin|Teamleiter(?:in)?\s+(?:Produktion|Entwicklung|Qualität|Vertrieb|Logistik|Fertigung)|Teamleiter|Teamleiterin|Einrichter|Einrichterin|Laborant|Laborantin|Betriebsratssprecher|Betriebsratssprecherin|Meister|Meisterin|Geschäftsführer|Geschäftsführerin|Projektleiter|Projektleiterin|Abteilungsleiter|Abteilungsleiterin|Bereichsleiter|Bereichsleiterin|Betriebsleiter|Betriebsleiterin|Werkleiter|Werkleiterin|Produktionsleiter|Produktionsleiterin|Qualitätsleiter|Qualitätsleiterin|Assistent|Assistentin|Ansprechpartner|Ansprechpartnerin|Prüfer|Prüferin|Monteur|Monteurin|Inbetriebnehmer|Inbetriebnehmerin|Vorstand|Mme|Mlle|Me|Monsieur|Madame|Mademoiselle|Maître|Don|Doña|Señor|Señora|Avvocato|Dottore|Dottoressa|Ingegnere|Signor|Signora|Dhr|Mevr|Avv|Dott|Ing|Sig|D|Dña|Sr|Sra|Srta)(?:\.|\b)[ \t\xA0]+((?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:\p{Lu}\.(?:[ \t\xA0]*\p{Lu}\.)*|(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+)(?:[ \t\xA0]+(?:\p{Lu}\.|(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+)){0,3})(?![‘'’–-][\p{L}0-9])(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
301
+ { name: 'Names with Honorifics', type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?:Kollege|Kollegin|Instandhalter|Instandhalterin|Disponent|Disponentin|Werkstudent|Werkstudentin|Lagerleiter|Lagerleiterin|Arbeitsvorbereiter|Arbeitsvorbereiterin|Qualitätsprüfer|Qualitätsprüferin|Dott\.ssa|Sig\.ra|Mr|Mrs|Ms|Miss|Dr|Prof|Hon|Rev|Judge|Atty|Doctor|Professor|Sir|Lord|Lady|Dame|Herr|Frau|Rechtsanwalt|Rechtsanwältin|Notar|Schichtleiter|Schichtleiterin|Schichtführer|Schichtführerin|Teamleiter(?:in)?\s+(?:Produktion|Entwicklung|Qualität|Vertrieb|Logistik|Fertigung)|Teamleiter|Teamleiterin|Einrichter|Einrichterin|Laborant|Laborantin|Betriebsratssprecher|Betriebsratssprecherin|Meister|Meisterin|Geschäftsführer|Geschäftsführerin|Projektleiter|Projektleiterin|Abteilungsleiter|Abteilungsleiterin|Bereichsleiter|Bereichsleiterin|Betriebsleiter|Betriebsleiterin|Werkleiter|Werkleiterin|Produktionsleiter|Produktionsleiterin|Qualitätsleiter|Qualitätsleiterin|Assistent|Assistentin|Ansprechpartner|Ansprechpartnerin|Prüfer|Prüferin|Monteur|Monteurin|Inbetriebnehmer|Inbetriebnehmerin|Vorstand|Mme|Mlle|Me|Monsieur|Madame|Mademoiselle|Maître|Don|Doña|Señor|Señora|Avvocato|Dottore|Dottoressa|Ingegnere|Signor|Signora|Dhr|Mevr|Avv|Dott|Ing|Sig|D|Dña|Sr|Sra|Srta)(?:\.|\b)[ \t\xA0]+((?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:\p{Lu}\.(?:[ \t\xA0]*\p{Lu}\.)*|(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+)(?:[ \t\xA0]+(?:\p{Lu}\.|(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+)){0,3})(?![‘'’–-][\p{L}0-9])(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
218
302
  // Dutch / European Inverted Format (e.g. "Ruiter, J. de", "Berg, S. van den", "Gameren, P. van")
219
303
  { name: 'Dutch Inverted Administrative Name', type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}[\p{Ll}'’–-]+(?:-[\p{Lu}\p{Ll}'’–-]+)?,[ \t\xA0]+(?:\p{Lu}\.(?:[ \t\xA0]*\p{Lu}\.)*|\p{Lu}[\p{Ll}'’–-]+)[ \t\xA0]+(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)(?=[^\p{L}\p{N}_]|$)/gu },
220
304
  // European & Initial-Prefixed Names (e.g. "J. de Ruiter", "P. van Gameren", "J.A. van der Meer", "M. de Jong", "J. Smith")
221
- { type: 'NAME', isAggressiveName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)(?:\p{Lu}\.(?:[ \t\xA0]*\p{Lu}\.)*)(?!\s*(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b)[ \t\xA0]+(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{Ll}'’]+(?:-\p{Lu}[\p{Ll}'’]+)*)(?:'s)?(?![‘'’–-][\p{L}0-9])(?![&/]\p{L})(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
305
+ { type: 'NAME', isAggressiveName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?<!\b(?:Fed|Federal|State|Local|U\.?S\.?)\.?\s+)(?:\p{Lu}\.(?:[ \t\xA0]*\p{Lu}\.)*)(?!\s*(?:Civ|Crim|App|Evid|Bankr|Pro|Proc|Jud)\b)[ \t\xA0]+(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)[ \t\xA0]+){0,2}(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}\p{Ll}+(?:['’]\p{L}+)*(?:-\p{Lu}\p{Ll}+(?:['’]\p{L}+)*)*)(?:'s)?(?![‘'’–-][\p{L}0-9])(?![&/]\p{L})(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
222
306
  // Generalized Name Detection (First [Middle] Last) — supporting European particles, apostrophes (d', l', Dell', D', O', M'), Celtic (Mc, Mac, Fitz), multi-hyphens, and Spanish conjunctions
223
- { type: 'NAME', isAggressiveName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?!(?:Brief|Dossier|Schreiben|Bericht|Akte|Urteil|Vertrag|Notiz|Auftrag|Fertigungsauftrag|Lieferschein|Bestellung|Rechnung|Ticket|Rapport|Document|Lettre|Acta|Sentencia)[ \t\xA0]+(?:von|van|de|du|di|da|del)\b)(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{Ll}'’]*(?:-\p{Lu}[\p{Ll}'’]*)*)(?:[ \t\xA0]+(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)|(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{Ll}'’]*(?:-\p{Lu}[\p{Ll}'’]*)*)|\p{Lu}\.?)){0,2}[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{Ll}'’]*(?:-\p{Lu}[\p{Ll}'’]*)*)(?:[ \t\xA0]+(?:y|i)[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{Ll}'’]*(?:-\p{Lu}[\p{Ll}'’]*)*))?(?:'s)?(?![‘'’–-][\p{L}0-9])(?![&/]\p{L})(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
307
+ { type: 'NAME', isAggressiveName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?!(?:Brief|Dossier|Schreiben|Bericht|Akte|Urteil|Vertrag|Notiz|Auftrag|Fertigungsauftrag|Lieferschein|Bestellung|Rechnung|Ticket|Rapport|Document|Lettre|Acta|Sentencia)[ \t\xA0]+(?:von|van|de|du|di|da|del)\b)(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}\p{Ll}*(?:['’]\p{L}+)*(?:-\p{Lu}\p{Ll}*(?:['’]\p{L}+)*)*)(?:[ \t\xA0]+(?:(?:von[ \t\xA0]+und[ \t\xA0]+zu|von[ \t\xA0]+der|van[ \t\xA0]+de(?:n|r)?|van[ \t\xA0]+['’]t|in[ \t\xA0]+['’]t|de[ \t\xA0]+l['’]|de[ \t\xA0]+la[ \t\xA0]+(?:los|las)?|de[ \t\xA0]+(?:la|las|los)|['’]t|(?:Dell|Dall|Sant|[DdLlOoMm])['’]|van|von|zu|vom|auf|de|di|da|do|dos|das|la|le|del|dels|du|der|den|te|ten|ter|af|av)|(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}\p{Ll}*(?:['’]\p{L}+)*(?:-\p{Lu}\p{Ll}*(?:['’]\p{L}+)*)*)|\p{Lu}\.?)){0,2}[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}\p{Ll}*(?:['’]\p{L}+)*(?:-\p{Lu}\p{Ll}*(?:['’]\p{L}+)*)*)(?:[ \t\xA0]+(?:y|i)[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}\p{Ll}*(?:['’]\p{L}+)*(?:-\p{Lu}\p{Ll}*(?:['’]\p{L}+)*)*))?(?:'s)?(?![‘'’–-][\p{L}0-9])(?![&/]\p{L})(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
224
308
  // Payroll & Inverted Format Names (e.g. "BARKER, KELLY", "Vance-Soto, Eleanor", "Lefèvre-Saint-Germain, Hélène")
225
309
  { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}[\p{L}'’–-]+(?:-[\p{Lu}\p{Ll}'’–-]+)?,[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+)(?:[ \t\xA0]+\p{Lu}\.?(?!\p{L})|[ \t\xA0]+(?:(?:(?:Dell|Dall|Sant|[DdLlOoMm])['’]|(?:Mc|Mac|Fitz))?\p{Lu}[\p{L}'’–-]+))*(?=[^\p{L}\p{N}_]|$)/gu },
226
310
  // All-Caps Names (2–3 words, supporting single-letter middle initial e.g. "KELLY M BARKER", "JOHN M BARKER")
227
- { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:[ \t\xA0]+(?:\p{Lu}\.?|[A-Z][a-z]+))?[ \t\xA0]+\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
311
+ { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:[ \t\xA0]+(?:\p{Lu}\.?|[A-Z][a-z]+))?[ \t\xA0]+\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
228
312
  // ALL-CAPS first + middle initial(s) with optional spaces + ALL-CAPS last name
229
- { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:[ \t\xA0]*\p{Lu}\.)+[ \t\xA0]*\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
313
+ { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:[ \t\xA0]*\p{Lu}\.)+[ \t\xA0]*\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
230
314
  // ALL-CAPS first name + optional middle initial(s) with optional spaces + Mixed-Case last name
231
- { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:(?:[ \t\xA0]*\p{Lu}\.)+[ \t\xA0]*|[ \t\xA0]+)(?:\p{Lu}\p{Ll}[\p{Ll}'-]*|\p{Lu}[\p{Ll}'-]*[\p{Lu}'-][\p{Ll}'-]*)(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
315
+ { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])\p{Lu}{2,}(?:[\p{Lu}'-]*\p{Lu})?(?:(?:[ \t\xA0]*\p{Lu}\.)+[ \t\xA0]*|[ \t\xA0]+)(?:\p{Lu}\p{Ll}[\p{Ll}'-]*|\p{Lu}[\p{Ll}'-]*[\p{Lu}'-][\p{Ll}'-]*)(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
232
316
  // Names with Quoted or Parenthetical Nicknames (e.g. Judith "Judy" Diaz, Robert (Bob) Smith)
233
- { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])(?:\p{Lu}[\p{Ll}'-]+)[ \t\xA0]+(?:"[\p{Lu}\p{Ll}'-]+"|'[\p{Lu}\p{Ll}'-]+'|[“‘][\p{Lu}\p{Ll}'-]+[”’]|\([\p{Lu}\p{Ll}'-]+\))[ \t\xA0]+(?:\p{Lu}[\p{Ll}'-]+)(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
317
+ { type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])(?:\p{Lu}[\p{Ll}'-]+)[ \t\xA0]+(?:"[\p{Lu}\p{Ll}'-]+"|'[\p{Lu}\p{Ll}'-]+'|[“‘][\p{Lu}\p{Ll}'-]+[”’]|\([\p{Lu}\p{Ll}'-]+\))[ \t\xA0]+(?:\p{Lu}[\p{Ll}'-]+)(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*[:$€£¥₪₽₹])/gu },
234
318
 
235
319
  // W-2 Box c Employer Block (Name, Address, and Zip Code)
236
320
  { type: 'NAME', isContextName: true, regex: /(?:(?:\bBox\s+c\b|\bc\.\s*|\bc\s+(?=Employer))\s*(?:Employer(?:'s)?\s*)?(?:name[,\s]+address[,\s]+and\s+ZIP\s+code|name)?|(?:Employer(?:'s)?\s+name[,\s]+address[,\s]+and\s+ZIP\s+code))[\s:#]*([A-Za-z0-9&., \t\xA0'-]{2,45}?)(?=\r?\n|$|\s{3,}|\t|EIN|FEIN|Box|\d+\b|Wages|Federal|Social|Medicare)/gi },
@@ -240,6 +324,8 @@ let REGEX_RULES = [
240
324
  { type: 'NAME', isContextName: true, regex: /(?:(?:Your|Employee(?:'s)?|Borrower(?:'s)?|Co-Borrower(?:'s)?|Applicant(?:'s)?|Candidate(?:'s)?|Worker(?:'s)?|Taxpayer(?:'s)?|Spouse(?:'s)?|Person(?:'s)?)\s+)?(?:First\s+name(?:\s+(?:and|&)\s+(?:middle\s+)?initial)?|Given\s+name)[\s:#]+(?:\b|\b\s*)([A-Za-z0-9.\s'-]{2,30}?)(?=\r?\n|$|\s{3,}|\t|Last|Surname|Family|Suff|Box|Address|SSN|EIN)/gi },
241
325
  // Contextual Last Names (Last name, Surname, Family name)
242
326
  { type: 'NAME', isContextName: true, regex: /(?:(?:Your|Employee(?:'s)?|Borrower(?:'s)?|Co-Borrower(?:'s)?|Applicant(?:'s)?|Candidate(?:'s)?|Worker(?:'s)?|Taxpayer(?:'s)?|Spouse(?:'s)?|Person(?:'s)?)\s+)?(?:Last\s+name|Surname|Family\s+name)[\s:#]+(?:\b|\b\s*)([A-Za-z'-]{2,30})/gi },
327
+ // JSON / API Key-Value Single & Multi-Token Names ("firstName": "Sarah", "lastName": "Connor")
328
+ { type: 'NAME', isContextName: true, regex: /(?<=["'](?:firstName|first_name|lastName|last_name|middleName|middle_name|givenName|given_name|familyName|family_name|surname|fullName|full_name|customerName|customer_name|clientName|client_name|patientName|patient_name|employeeName|employee_name|contactName|contact_name)["']\s*:\s*["'])([A-Z\p{Lu}][\p{L}'’.-]{1,35}(?:[ \t\xA0]+[A-Z\p{Lu}][\p{L}'’.-]{1,35}){0,3})(?=["'])/gu },
243
329
  // Contextual General Names (Employee, Principal Investigator, Physician, Doctor, etc.)
244
330
  { type: 'NAME', isContextName: true, regex: /(?:(?:Employee|Employer|Borrower|Co-Borrower|Applicant|Candidate|Worker|Taxpayer|Spouse|Client|Customer|Patient|Claimant|Insured|Tenant|Landlord|Buyer|Seller|Plaintiff|Defendant|Testator|Author|(?:Subject\s+)?Personnel)\s+(?:Name|Full\s+Name)|(?:Principal\s+Investigator|Investigator|Physician|Doctor|Supervisor|Reporting\s+To|Prepared\s+By|Attention|Attn|(?<!Emergency\s+|ICE\s+)Contact(?:\s+Person)?|Author)(?:\s+Name)?|(?:Employee|Employer|Borrower|Client|Customer|Patient|Claimant|Insured|Doctor|Attn|Signatory|Witness|Tenant|Landlord|Buyer|Seller|Plaintiff|Defendant|Author|(?:Subject\s+)?Personnel)\s*[:=#])[:\s#|]*(?:\b|\b\s*)([A-Z\p{Lu}][A-Za-z0-9&.,\s'"“”()-]{1,35}?)(?=\r?\n|$|\s{3,}|\t|Employee|Employer|Address|Phone|SSN|EIN|FEIN|Date|Pay|Rate|Tax|W-2|OMB|Copy|Box|Status|\|)/giu },
245
331
  // Contextual Nicknames & Preferred Names (e.g. "Nickname: Judy", "Preferred Name: Alex", "AKA: Bob", "Goes by: Sam")
@@ -360,11 +446,13 @@ let PROFILE_RULES = {
360
446
  { type: 'ID', regex: /\b(?:CRD|CIK|Advisor\s*ID|Broker\s*ID)[:\s#]*(\d{5,10})\b/gi },
361
447
  { type: 'ID', regex: /\b(?:LEI|LEGAL[\s-]ENTITY[\s-]IDENTIFIER)[:\s#-]*[0-9A-Z]{20}\b/gi },
362
448
  { type: 'ID', regex: /\bBBG[0-9A-Z]{9}\b/g },
363
- { type: 'FINANCIAL', regex: /(?<=\b(?:Settlement(?:\s+Amount)?|Wire(?:\s+(?:Transfer|Amount))?|Direct\s+Wire|Transaction(?:\s+Amount)?|Payment(?:\s+Amount)?)[ \t:]+)(?:(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))/gi },
364
- { type: 'FINANCIAL', regex: /(?<=(?:^|[\r\n\t.;])\s*(?<!\b(?:Trade(?:\s+execution)?|Net|Gross|Base|Loan|Transfer)\s+)(?:Wire(?:\s+(?:Transfer|Amount))?|Direct\s+Wire|Balance|Deposit|Withdrawal|Settlement|Principal|Interest|Remittance|Payout|(?:Capital\s+|Annual\s+)?Distribution(?:s)?|Transaction(?:\s+Amount)?|Payment(?:\s+Amount)?|Total(?:\s+Due)?|Amount(?:\s+Due)?)[:\s]+)(?:(?:\((?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:[$€£¥₪₽₹]\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:[$€£¥₪₽₹]\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))|[0-9,.'’]{3,}\b)/gi },
365
- { type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?(?:\s*(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|[$€£¥₪₽₹]))?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\b|[$€£¥₪₽₹]))/gi },
449
+ { type: 'FINANCIAL', regex: /(?<=\b(?:Settlement(?:\s+Amount)?|Wire(?:\s+(?:Transfer|Amount))?|Direct\s+Wire|Transaction(?:\s+Amount)?|Payment(?:\s+Amount)?)[ \t:]+)(?:(?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])|(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s?)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])))/gi },
450
+ { type: 'FINANCIAL', regex: /(?<=(?:^|[\r\n\t.;])\s*(?<!\b(?:Trade(?:\s+execution)?|Net|Gross|Base|Loan|Transfer)\s+)(?:Wire(?:\s+(?:Transfer|Amount))?|Direct\s+Wire|Balance|Deposit|Withdrawal|Settlement|Principal|Interest|Remittance|Payout|(?:Capital\s+|Annual\s+)?Distribution(?:s)?|Transaction(?:\s+Amount)?|Payment(?:\s+Amount)?|Total(?:\s+Due)?|Amount(?:\s+Due)?)[:\s]+)(?:(?:\((?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])))|[0-9,.'’]{3,}\b)/gi },
451
+ { type: 'FINANCIAL', regex: /(?:(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])\s*)(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?(?:\s*(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))?\b|\b(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?\s*(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))/gi },
452
+ { type: 'FINANCIAL', regex: /(?:(?:\$\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\s*\bUSD\b|\bUSD\s*\$?[0-9,.'’\u2018\u2019]*\d[KMB]?\b)/gi },
453
+ { type: 'FINANCIAL', regex: /(?<=\b(?:(?:ending|opening|current|available|ledger|account)\s+)?(?:balance|wire|settlement|distribution|withdrawal|payout|remittance)\s*(?:was|is|of|[:=])\s*)(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
366
454
  // Corporate Financial Performance, P&L Statements, Balance Sheets & Earnings
367
- { type: 'FINANCIAL', regex: /(?<=\b(?<!\b(?:EV|Debt|Net\s+Debt)\/)(?:Revenue|Total\s+Revenue|Sales\s+Revenue|Cost\s+of\s+Goods(?:\s+Sold)?|COGS|Gross\s+Profit|Operating\s+Expenses?|OpEx|Operating\s+Income(?:\s*\([^)]*\))?|Operating\s+Loss|Net\s+Income(?:\s*[/()]*\s*Loss)?|Net\s+Loss|Foreign\s+Exchange(?:\s+Loss)?|FX\s+Loss|Retained\s+Earnings|Total\s+Equity|Shareholders?'?\s+Equity|Total\s+Assets|Total\s+Liabilities|Accounts\s+Payable|Accounts\s+Receivable|Cash|Cash\s+Flow|Free\s+Cash\s+Flow|Capital\s+Expenditures?|CapEx|Dividends?|EBITDA|EBIT|Balance|Loss|Profit)[:|\s]+)(?:(?:\((?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:[$€£¥₪₽₹]\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:[$€£¥₪₽₹]\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))|[0-9,.'’]*\d\b)(?![ \t\xA0]*(?:%|[xX]\b|times\b|bps\b))/gi },
455
+ { type: 'FINANCIAL', regex: /(?<=\b(?<!\b(?:EV|Debt|Net\s+Debt)\/)(?:Revenue|Total\s+Revenue|Sales\s+Revenue|Cost\s+of\s+Goods(?:\s+Sold)?|COGS|Gross\s+Profit|Operating\s+Expenses?|OpEx|Operating\s+Income(?:\s*\([^)]*\))?|Operating\s+Loss|Net\s+Income(?:\s*[/()]*\s*Loss)?|Net\s+Loss|Foreign\s+Exchange(?:\s+Loss)?|FX\s+Loss|Retained\s+Earnings|Total\s+Equity|Shareholders?'?\s+Equity|Total\s+Assets|Total\s+Liabilities|Accounts\s+Payable|Accounts\s+Receivable|Cash|Cash\s+Flow|Free\s+Cash\s+Flow|Capital\s+Expenditures?|CapEx|Dividends?|EBITDA|EBIT|Balance|Loss|Profit)[:|\s]+)(?:(?:\((?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[$€£¥₪₽₹])))|[0-9,.'’]*\d\b)(?![ \t\xA0]*(?:%|[xX]\b|times\b|bps\b))/gi },
368
456
  // Interbank Wire Transcripts & Payment Messaging
369
457
  { type: 'FINANCIAL', regex: /\b(?:(?:SWIFT(?:\s+MT\d{3})?\s+)?(?:Field\s+2[01]|F2[01])(?:\s+Transaction\s+Reference)?|TRN|Transaction\s+Reference(?:\s+Number)?|SWIFT\s+Ref)[:\s#-]+([A-Za-z0-9\/-]{8,30})\b/gi },
370
458
  { type: 'FINANCIAL', regex: /\bFT\d{10,14}\b/gi },
@@ -385,11 +473,18 @@ let PROFILE_RULES = {
385
473
  { type: 'ID', regex: /\b(?:Lockbox|DACA|Deposit\s+Account\s+Control\s+Agreement|Blocked\s+Account(?:\s+Agreement)?)[ \t\xA0]*(?:Account|ID|Ref|No\.?|#)?[ \t\xA0]*[:#-][ \t\xA0]*([A-Za-z0-9-]{4,25})\b/gi }
386
474
  ],
387
475
  medical: [
388
- { type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?:PATIENT|Pt\.?|LEGAL\s+GUARDIAN(?:\/PROXY)?|PROXY|EMERGENCY\s+CONTACT|(?:ATTENDING|CONSULTING|ADMITTING|REFERRING|TREATING|ORDERING|PRIMARY\s+CARE|RESIDENT|STAFF|SUPERVISING|EMERGENCY)\s+(?:PHYSICIAN|PROVIDER|DOCTOR|SURGEON|CLINICIAN|SPECIALIST|NEUROLOGIST|CARDIOLOGIST|ONCOLOGIST|RADIOLOGIST|PATHOLOGIST|PSYCHIATRIST|PEDIATRICIAN|ANESTHESIOLOGIST|HOSPITALIST|PCP)|ATTENDING|SURGEON|PHYSICIAN|PROVIDER|CLINICIAN|PCP|(?:CLINICAL\s+)?SCRIBE|TRANSCRIBED\s+BY|DICTATED\s+BY|NURSE\s+PRACTITIONER|PHYSICIAN\s+ASSISTANT|REGISTERED\s+NURSE|SUBSCRIBER|GUARDIAN)\s*[:#|]?\s*(?!\b(?:NPI|MRN|DOB|ID|DATE|RECORD|PHONE)\b)(?:(?:Dr|Doctor|Prof|Professor|Mr|Mrs|Ms)\.?\s+)?((?:(?:d['’]|l['’]|(?:(?:van|von|de|di|da|do|dos|das|la|le|del|dels|du|der|den|ten|ter|af|av)\s+)+)\s*)?\p{Lu}[\p{L}'’–-]*(?:-[\p{Lu}\p{Ll}'’–-]+)*(?:,\s+(?:(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]+)(?:\s+\p{Lu}\.?)?(?:,\s*(?:Jr\.?|Sr\.?|III|IV|II))?|\s+(?:(?:van|von|de|di|da|do|dos|das|la|le|del|du|der|d['’]|l['’]|de\s+la|de\s+los|von\s+der)\s+)?(?:(?:\p{Lu}\.?\s+)?(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]*(?:-[\p{Lu}\p{Ll}'’–-]+)*))(?:\s+(?:y|i)\s+(?:(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]+))?)(?=[ \t\xA0]*,?[ \t\xA0]*(?:(?:MD|DO|MBBS|PhD|MSN|BSN|RN|NP|FNP|DNP|PA-C|PA|DDS|DMD|PharmD|OD|DPT|DC|CRNA|CNM|FACS|FAAP|FACC|FACP)\b|[^\p{L}\p{N}]|$))/giu },
476
+ { type: 'NAME', isContextName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?:PATIENT|Pt\.?|PACIENTE|M[ÉE]DICO(?:\s+(?:RESPONS[ÁA]VEL|ASSISTENTE|SOLICITANTE))?|CIRURGI[ÃA]O|OFTALMOLOGISTA|DOUTOR(?:A)?|RESPONS[ÁA]VEL(?:\s+LEGAL)?|LEGAL\s+GUARDIAN(?:\/PROXY)?|PROXY|EMERGENCY\s+CONTACT|(?:ATTENDING|CONSULTING|ADMITTING|REFERRING|TREATING|ORDERING|PRIMARY\s+CARE|RESIDENT|STAFF|SUPERVISING|EMERGENCY)\s+(?:PHYSICIAN|PROVIDER|DOCTOR|SURGEON|CLINICIAN|SPECIALIST|NEUROLOGIST|CARDIOLOGIST|ONCOLOGIST|RADIOLOGIST|PATHOLOGIST|PSYCHIATRIST|PEDIATRICIAN|ANESTHESIOLOGIST|HOSPITALIST|PCP)|ATTENDING|SURGEON|PHYSICIAN|PROVIDER|CLINICIAN|PCP|(?:CLINICAL\s+)?SCRIBE|TRANSCRIBED\s+BY|DICTATED\s+BY|NURSE\s+PRACTITIONER|PHYSICIAN\s+ASSISTANT|REGISTERED\s+NURSE|SUBSCRIBER|GUARDIAN)\s*[:#|]?\s*(?!\b(?:NPI|MRN|DOB|ID|DATE|RECORD|PHONE|CPF|CNS|CRM)\b)(?:(?:Dr\.?a?|Doctor|Doutor(?:a)?|Prof\.?a?|Professor|Mr|Mrs|Ms|Sr\.?a?)\.?\s+)?((?:(?:d['’]|l['’]|(?:(?:van|von|de|di|da|do|dos|das|la|le|del|dels|du|der|den|ten|ter|af|av)\s+)+)\s*)?\p{Lu}[\p{L}'’–-]*(?:-[\p{Lu}\p{Ll}'’–-]+)*(?:,\s+(?:(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]+)(?:\s+\p{Lu}\.?)?(?:,\s*(?:Jr\.?|Sr\.?|Filho|Neto|Sobrinho|III|IV|II))?|\s+(?:(?:van|von|de|di|da|do|dos|das|la|le|del|du|der|d['’]|l['’]|de\s+la|de\s+los|von\s+der)\s+)?(?:(?:\p{Lu}\.?\s+)?(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]*(?:-[\p{Lu}\p{Ll}'’–-]+)*))(?:\s+(?:y|i|e)\s+(?:(?:d['’]|l['’])?\p{Lu}[\p{L}'’–-]+))?)(?=[ \t\xA0]*,?[ \t\xA0]*(?:(?:MD|DO|MBBS|PhD|MSN|BSN|RN|NP|FNP|DNP|PA-C|PA|DDS|DMD|PharmD|OD|DPT|DC|CRNA|CNM|FACS|FAAP|FACC|FACP|CRM|RQE)\b|[^\p{L}\p{N}]|$))/giu },
389
477
  // HL7 v2 PID-5 Caret-Delimited Patient / Provider Names (e.g. "Guillermo^Maricela^C", "Vance-Soto^Eleanor^M")
390
478
  { type: 'NAME', isContextName: true, isHL7Name: true, regex: /\b[A-Za-z'’-]{2,}\^[A-Za-z'’-]{2,}(?:\^[A-Za-z'’.-]+){0,4}\b/g },
391
479
  // HL7 v2 Segment Delimited Dates (YYYYMMDD in PID-7, MSH-7, etc. e.g. "||19820412|F")
392
480
  { type: 'DATE', regex: /(?<=[|:])(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])(?=[|:\s])/g },
481
+ // Brazil SUS / Cartão Nacional de Saúde (CNS - 15 digits starting with 1, 2, 7, 8, or 9)
482
+ { type: 'PHI', regex: /\b(?:(?:CNS|Cart[ãa]o\s+(?:Nacional\s+de\s+Sa[úu]de|SUS))[:\s#.-]*([12789]\d{2}[ \t.-]?\d{4}[ \t.-]?\d{4}[ \t.-]?\d{4})|([12789]\d{2}[ \t.-]\d{4}[ \t.-]\d{4}[ \t.-]\d{4}))\b/gi },
483
+ // Brazil Clinical Council Registrations (CRM, COREN, CRO, CRF, CREFITO, CRP, CRN, RQE)
484
+ { type: 'ID', regex: /\b(?:CRM|COREN|CRO|CRF|CREFITO|CRP|CRN|RQE)(?:[-/\s]+(?:AC|AL|AP|AM|BA|CE|DF|ES|GO|MA|MT|MS|MG|PA|PB|PR|PE|PI|RJ|RN|RS|RO|RR|SC|SP|SE|TO))?(?:\s*(?:n[º°o]?|#))?[:\s#.-]+(\d{3,10}(?:[-/][A-Za-z]{1,3})?)\b/gi },
485
+ { type: 'PHI', regex: /\b(?:Prontu[áa]rio|Atendimento|Registro\s+Hospitalar|Guia\s+TISS|Carteirinha(?:\s+do\s+Conv[êe]nio)?)[:\s#.-]+([A-Za-z0-9./_-]{3,25})\b/gi },
486
+ { type: 'PHI', regex: /\b(?:Accession(?:\s*(?:No\.?|Number|#|ID))?|ACC)\s*[:#=-]+\s*([A-Za-z0-9._/-]{4,25})\b/gi },
487
+ { type: 'PHI', regex: /\b(?:(?:Study|Series|SOP)\s*Instance\s*UID|(?:Study|Series|SOP)InstanceUID|DICOM\s+UID)\s*[:=#-]*\s*((?:1\.2\.840|2\.25)\.\d+(?:\.\d+){4,15})\b|\b(?:1\.2\.840\.10008|1\.2\.840\.113619)\.\d+(?:\.\d+){3,15}\b/gi },
393
488
  { type: 'PHI', regex: /\b(?:MRN|Patient|Medical\s+Record|EHR|Subject|Participant|Screening|Randomization|Cohort|Site)\s*(?:ID|Number|No\.?|#)?[:\s#.]+([A-Za-z0-9\/_-]+)/gi },
394
489
  { type: 'PHI', regex: /\b(?:SUBJ|PARTICIPANT|RAND|SCR)[-_][A-Za-z0-9\/_-]{3,}\b/gi },
395
490
  { type: 'DATE', regex: /\b(?:DOB|Date of Birth|BIRTHDAY)[\s:]+([0-9./-]{6,10})\b/gi },
@@ -1192,7 +1287,9 @@ let PROFILE_RULES = {
1192
1287
  { type: 'ID', regex: /\bCRD\s*#?\s*\d{4,8}\b/gi },
1193
1288
  { type: 'FINANCIAL', regex: /(?<=\b(?:(?:Capital\s+|Annual\s+)?(?:Distribution|Withdrawal)s?|RMD|Required\s+Minimum\s+Distribution)[:\s]+)(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
1194
1289
  { type: 'FINANCIAL', regex: /(?<=\b(?:Portfolio(?:\s+[Vv]alue)?|Market\s+Value|Net\s+Worth|AUM|Total\s+Assets|Cash\s+Balance|Account\s+Value|Capital\s+Gains?|Unrealized\s+(?:Gain|Loss)|Realized\s+(?:Gain|Loss)|Dividend\s+Income)[:\s]+)(?:(?:\((?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:[$€£¥₪₽₹]\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:[$€£¥₪₽₹]\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))|[0-9,.'’]{3,}\b)/gi },
1195
- { type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?(?:\s*(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|[$€£¥₪₽₹]))?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\b|[$€£¥₪₽₹]))/gi },
1290
+ { type: 'FINANCIAL', regex: /(?:(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])\s*)(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?(?:\s*(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))?\b|\b(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?\s*(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))/gi },
1291
+ { type: 'FINANCIAL', regex: /(?:(?:\$\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\s*\bUSD\b|\bUSD\s*\$?[0-9,.'’\u2018\u2019]*\d[KMB]?\b)/gi },
1292
+ { type: 'FINANCIAL', regex: /(?<=\b(?:(?:ending|opening|current|available|ledger|account)\s+)?(?:balance|wire|settlement|distribution|withdrawal|payout|remittance)\s*(?:was|is|of|[:=])\s*)[$€£¥₪₽₹]\s*[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
1196
1293
  { type: 'FINANCIAL', regex: /\b(?:Roth\s+)?(?:IRA|401k|401\(k\)|403b|403\(b\)|SEP|SIMPLE)\s*(?:Account|Acct|Plan)?\s*(?:(?:No\.?|Number|#)[:\s#]*|[:#]\s*)([A-Z0-9-]{4,20})\b/gi },
1197
1294
  { type: 'ID', regex: /\bCUSIP[:\s#]*([0-9A-Z]{9})\b/gi },
1198
1295
  { type: 'ID', regex: /\bISIN[:\s#]*([A-Z]{2}[0-9A-Z]{9}[0-9])\b/gi },
@@ -1280,7 +1377,9 @@ let PROFILE_RULES = {
1280
1377
  },
1281
1378
  // Existing core insurance patterns
1282
1379
  { type: 'FINANCIAL', regex: /(?<=\b(?:Loss|Claim\s+Amount|Claim\s+Settlement|Settlement|(?<!\b(?:Case|Outstanding|IBNR|Loss)\s+)Reserve|Indemnity)[:\s]+)(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
1283
- { type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?(?:\s*(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|[$€£¥₪₽₹]))?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\b|[$€£¥₪₽₹]))/gi },
1380
+ { type: 'FINANCIAL', regex: /(?:(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])\s*)(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?(?:\s*(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))?\b|\b(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?\s*(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))/gi },
1381
+ { type: 'FINANCIAL', regex: /(?:(?:\$\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\s*\bUSD\b|\bUSD\s*\$?[0-9,.'’\u2018\u2019]*\d[KMB]?\b)/gi },
1382
+ { type: 'FINANCIAL', regex: /(?<=\b(?:(?:ending|opening|current|available|ledger|account)\s+)?(?:balance|wire|settlement|distribution|withdrawal|payout|remittance)\s*(?:was|is|of|[:=])\s*)[$€£¥₪₽₹]\s*[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
1284
1383
  { type: 'ID', regex: /\b(?:(?:Assigned\s+)?Adjuster\s+(?:ID|No)|Claim\s+Rep(?:\.|resentative)?)[:\s#]*[-A-Z0-9]{4,15}\b/gi },
1285
1384
  { type: 'ID', regex: /\bNAIC(?:\s+(?:Company\s+)?(?:Code|CoCode|No\.?|Number|#))?[:\s#]*(\d{5})\b/gi },
1286
1385
  { type: 'FINANCIAL', regex: /(?<=\b(?:Deductible|Premium|Coverage\s+Amount)[:\s]+)(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
@@ -1314,7 +1413,9 @@ let PROFILE_RULES = {
1314
1413
  { type: 'ID', regex: /\b(?:Invoice|Inv|Purchase\s+Order|PO|Bill|Check|Voucher|Remittance)\s*(?:Number|Num|No\.?|#)?[:\s#]+([A-Za-z0-9_-]{4,25})\b/gi },
1315
1414
  { type: 'FINANCIAL', regex: /(?<=\b(?<!\b(?:EV|Debt|Net\s+Debt)\/)(?:Invoice(?:\s+Total)?|Amount\s+Due|Total(?:\s+Due)?|Payment(?:\s+Amount)?|Ledger\s+Balance|Credit|Debit|Net\s+Amount|Total\s+Paid|Billed|Remittance|Total\s+Revenue|Revenue|Cost\s+of\s+Goods(?:\s+Sold)?|COGS|Gross\s+Profit|Operating\s+Expenses?|OpEx|Operating\s+Income(?:\s*\([^)]*\))?|Operating\s+Loss|Net\s+Income(?:\s*[/()]*\s*Loss)?|Net\s+Loss|Foreign\s+Exchange(?:\s+Loss)?|FX\s+Loss|Retained\s+Earnings|Total\s+Equity|Shareholders?'?\s+Equity|Total\s+Assets|Total\s+Liabilities|Accounts\s+Payable|Accounts\s+Receivable|Cash|Cash\s+Flow|Free\s+Cash\s+Flow|Capital\s+Expenditures?|CapEx|Dividends?|(?:Capital\s+|Annual\s+)?Distribution(?:s)?|Withdrawal(?:s)?|Settlement(?:s)?|Sales\s+Revenue|EBITDA|EBIT|Balance|Loss|Profit)[:|\s]+)(?:(?:\((?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:[$€£¥₪₽₹]\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:[$€£¥₪₽₹]\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))|[0-9,.'’]*\d\b)(?![ \t\xA0]*(?:%|[xX]\b|times\b|bps\b))/gi },
1316
1415
  { type: 'FINANCIAL', regex: /(?<=\|\s*)(?:(?:\((?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]+[KMB]?\)|(?:[$€£¥₪₽₹]\s*)\([0-9,.'’\u2018\u2019]+[KMB]?\))|(?:[-−](?:[$€£¥₪₽₹]\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\b|(?:[$€£¥₪₽₹]\s*)[-−][0-9,.'’\u2018\u2019]*\d[KMB]?\b)|(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])))(?=\s*\|)/gi },
1317
- { type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?(?:\s*(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|[$€£¥₪₽₹]))?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\b|[$€£¥₪₽₹]))/gi }
1416
+ { type: 'FINANCIAL', regex: /(?:(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])\s*)(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?(?:\s*(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))?\b|\b(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?\s*(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))/gi },
1417
+ { type: 'FINANCIAL', regex: /(?:(?:\$\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\s*\bUSD\b|\bUSD\s*\$?[0-9,.'’\u2018\u2019]*\d[KMB]?\b)/gi },
1418
+ { type: 'FINANCIAL', regex: /(?<=\b(?:(?:ending|opening|current|available|ledger|account)\s+)?(?:balance|wire|settlement|distribution|withdrawal|payout|remittance)\s*(?:was|is|of|[:=])\s*)[$€£¥₪₽₹]\s*[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi }
1318
1419
  ],
1319
1420
  pharma: [
1320
1421
  // Contextual Clinical, Regulatory & Pharmacovigilance Personnel
@@ -1598,14 +1699,16 @@ let PROFILE_RULES = {
1598
1699
  { type: 'ID', regex: /\b(?:Ineligible\s+AR|Eligible\s+AR|Collateral\s+Account)(?:\s+(?:Account|Acct|ID|Ref|No\.?|Number|#))*\s*[:#-]\s*([A-Za-z0-9-]{4,20})\b/gi },
1599
1700
  { type: 'ID', regex: /\b(?:Merchant\s+Cash\s+Advance|MCA)[ \t\xA0]*(?:Agreement|Contract)?[ \t\xA0]*(?:ID|Ref|No\.?|Number|#)?[ \t\xA0]*[:#-][ \t\xA0]*(?!Agreement\b)([A-Za-z0-9-]{4,25})\b/gi },
1600
1701
  { type: 'FINANCIAL', regex: /(?<=(?:^|[\r\n\t.;])[ \t\xA0]*(?:Daily|Weekly)(?:[ \t\xA0]+(?:ACH|Remittance|Debit|Payment|Transfer|Split(?:[ \t\xA0]+Funding)?|Holdback))+[ \t\xA0]*[:=][ \t\xA0]*)((?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’]+)/gi },
1601
- { type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’\u2018\u2019]*\d[KMB]?(?:\s*(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|[$€£¥₪₽₹]))?\b|\b\d[0-9,.'’\u2018\u2019]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY)\b|[$€£¥₪₽₹]))/gi },
1702
+ { type: 'FINANCIAL', regex: /(?:(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\s*|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])\s*)(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?(?:\s*(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))?\b|\b(?:\d[0-9,.'’\u2018\u2019]*\d|\d)[KMB]?\s*(?:\b(?:EUR|GBP|CHF|ILS|RUB|CAD|AUD|JPY|CNY|INR|BRL)\b|(?:R\$|[A-Z]{1,2}\$|[€£¥₪₽₹])))/gi },
1703
+ { type: 'FINANCIAL', regex: /(?:(?:\$\s*)?[0-9,.'’\u2018\u2019]*\d[KMB]?\s*\bUSD\b|\bUSD\s*\$?[0-9,.'’\u2018\u2019]*\d[KMB]?\b)/gi },
1704
+ { type: 'FINANCIAL', regex: /(?<=\b(?:(?:ending|opening|current|available|ledger|account)\s+)?(?:balance|wire|settlement|distribution|withdrawal|payout|remittance)\s*(?:was|is|of|[:=])\s*)[$€£¥₪₽₹]\s*[0-9,.'’\u2018\u2019]*\d[KMB]?\b/gi },
1602
1705
  { type: 'NAME', isContextName: true, isCorporateName: true, regex: /(?:MCA\s+Funder|Funder|ISO\s+Broker|Funding\s+Partner)[ \t\xA0]*[:#][ \t\xA0]*([A-Za-z0-9&.,\s'-]{2,40}?)(?=\r?\n|$|\s{3,}|\t|Rate|Factor|Advance|Amount|Terms)/gi }
1603
1706
  ]
1604
1707
  };
1605
1708
 
1606
1709
  let NAME_STOP_LIST = new Set([
1607
1710
  'clinical note', 'case note', 'prod log', 'siem alert', 'critical security incident', 'security incident', 'hr review', 'crm export', 'bank statement', 'file export', 'database row', 'lease application', 'strategy export', 'action log', 'glossary query', 'tool comparison', 'call transcript', 'zendesk ticket', 'board minutes', 'agent context', 'config dump', 'database dump', 'patient note', 'medical record', 'admission note', 'discharge summary', 'progress note', 'hiring review', 'security audit', 'incident response', 'server log', 'system log', 'api response', 'error log', 'audit log', 'debug log',
1608
- 'due diligence', 'cap table', 'cap table ledger', 'series a', 'm&a', 'm&a due diligence',
1711
+ 'due diligence', 'cap table', 'cap table ledger', 'series a', 'm&a', 'm&a due diligence', 'trunk group', 'route group', 'trunk group name', 'sip trunk',
1609
1712
  'executive compensation', 'global mobility', 'equity grant', 'stock options', 'restricted stock units', 'vesting schedule', 'severance agreement', 'release of claims', 'retention bonus', 'sign-on bonus', 'labor condition application', 'permanent labor certification', 'alien registration number', 'certificate of sponsorship', 'whistleblower report', 'ethics hotline', 'change in control', 'golden parachute', 'deferred compensation', 'compa-ratio', 'turnover rate', 'attrition rate', 'time to hire', 'offer acceptance rate', 'human capital', 'people partner', 'compensation committee',
1610
1713
  'bill of lading', 'sea waybill', 'air waybill', 'ocean bill of lading', 'ocean bill', 'shipping instruction', 'booking confirmation', 'booking reference', 'customs declaration', 'entry summary', 'single administrative document', 'cbp entry', 'cbp 7501', 'ata carnet', 'internal transaction', 'movement reference', 'vessel manifest', 'cargo manifest', 'verified gross', 'gross mass', 'container seal', 'bolt seal', 'bonded warehouse', 'free trade zone', 'per diem', 'lumper fee', 'chassis fee', 'terminal handling', 'bunker adjustment', 'freight forwarder', 'customs broker', 'customs agent', 'port agent', 'notify party', 'importer of record', 'exporter of record', 'incoterms 2020', 'hague-visby', 'hague visby', 'hamburg rules', 'rotterdam rules', 'montreal convention', 'warsaw convention', 'cmr convention', 'solas vgm', 'tariff schedule', 'duty rate', 'harbor maintenance fee', 'merchandise processing fee',
1611
1714
  'funded debt', 'operating procedures', 'standard operating', 'equal credit', 'credit opportunity', 'statutory standards', 'financial covenant', 'covenant compliance',
@@ -1705,7 +1808,8 @@ let NAME_STOP_LIST = new Set([
1705
1808
  'regular', 'hours', 'holiday', 'overtime', 'commission', 'bonus', 'bonuses', 'records', 'record', 'statement', 'statements', 'rate', 'rates', 'current', 'ytd', 'benefits', 'taxable', 'pre-tax', 'post-tax', 'reimbursements', 'reimbursement', 'fica', 'oasdi', 'disability', 'unemployment', 'sui', 'sdi', 'std', 'ltd', 'exemptions', 'exemption', 'allowances', 'allowance', 'filing', 'status', 'single', 'married', 'head', 'household', 'advice', 'frequency', 'bi-weekly', 'biweekly', 'weekly', 'monthly', 'semi-monthly', 'direct', 'deposit', 'routing', 'box', 'boxes', 'code', 'control', 'omb', 'copy', 'instructions', 'information', 'deferred', 'adoption', 'statutory', 'third-party', 'sick', 'form', 'schedule', 'w-2', 'w2', 'w-4', 'w4', '1099', 'k-1', '1040', 'fed', 'med', 'fwt', 'swt', 'fed w/h', 'fed med', 'locality', 'state wages', 'state tax', 'local wages', 'local tax', 'allocated', 'nonqualified', 'suff', 'suffix', 'allocated tips', 'advance eic', 'advance eic payment', 'dependent care', 'dependent care benefits', 'nonqualified plans', 'statutory employee', 'retirement plan', 'third-party sick pay',
1706
1809
  'uk', 'eu', 'us', 'usa', 'dpa', 'ico', 'dpo', 'ciso', 'ssrn', 'elsevier', 'gdpr', 'ccpa', 'cpra', 'hipaa', 'soc2', 'iso27001', 'governance', 'regulatory', 'framework', 'mandate', 'mandates', 'guidance', 'statute', 'statutes', 'jurisdiction', 'jurisdictions', 'recital', 'article', 'articles', 'treatise', 'workstation', 'device', 'endpoint', 'blueprint', 'personal', 'cross-border', 'cross', 'border', 'transfer', 'retrieval', 'augmented', 'generation', 'limitation', 'discrimination', 'imposes', 'heavy', 'identifiable', 'bypasses', 'author', 'authors', 'contact', 'contacts', 'keywords', 'keyword', 'keys', 'key', 'separation', 'pre-print', 'preprint', 'abstract', 'structured', 'background', 'methods', 'results', 'discussion', 'conclusion', 'conclusions', 'introduction', 'overview', 'workflow', 'paradigm', 'nomenclature', 'repository', 'classification', 'phenotypes', 'biomedical', 'bioinformatics', 'identifiers', 'de-identification', 'sanitization', 'ephemeral', 'validation', 'validating', 'genomic', 'genomics', 'clinical', 'phenotype', 'target', 'corresponding', 'subject', 'biorxiv', 'medrxiv', 'arxiv', 'privacyscrubber', 'research', 'lead', 'architect', 'chief', 'founder', 'symbols', 'gene', 'genes', 'sample', 'barcode', 'pipeline', 'downstream', 'pedigree', 'trees', 'exposes', 'violates', 'protecting', 'anonymization', 'redacted', 'redaction', 'zero-trust', 'ztds',
1707
1810
  'conventional', 'model', 'models', 'potential', 'multi', 'million', 'dollar', 'dollars', 'surrogate', 'surrogates', 'geographic', 'subdivisions', 'subdivision', 'inference', 'response', 'responses', 'latency', 'benchmarks', 'benchmark', 'experimental', 'methodology', 'informatics', 'ethics', 'accountability', 'civil', 'rights', 'perimeter', 'network', 'requirements', 'requirement', 'statutory', 'categories', 'category', 'regulations', 'regulation', 'penalties', 'penalty', 'disclosure', 'disclosures', 'clinician', 'terminal', 'transport', 'inpatient', 'discharge', 'summaries', 'operative', 'reports', 'outpatient', 'psychiatric', 'telehealth', 'consultation', 'pathology', 'sequencing', 'laboratory', 'sensitivity', 'specificity', 'proxy', 'gateways', 'gateway', 'packet', 'packets', 'inspection', 'declarations', 'availability', 'competing', 'interests', 'interventions', 'multicenter', 'multi-center', 'institution', 'institutional', 'protocol', 'deviations', 'adverse', 'events', 'abbreviations', 'agents', 'agent', 'committee', 'committees', 'board', 'boards', 'nih', 'pubmed', 'central', 'open', 'access', 'policy', 'trial', 'trials', 'crf', 'crfs', 'ehr', 'ehrs', 'baa', 'baas', 'hitech', 'safe', 'harbor', 'section',
1708
- 'general liability', 'commercial property', 'workers compensation', 'excess casualty', 'inland marine', 'ocean marine', 'commercial auto', 'first notice of loss', 'first notice', 'incident report', 'incurred losses', 'case reserve', 'loss run', 'loss history', 'subrogation claim', 'salvage recovery', 'treaty cession', 'reinsurance treaty', 'retention limit', 'public adjuster', 'independent adjuster', 'lead underwriter', 'producing agent', 'additional insured', 'named insured', 'injured worker', 'tpa examiner']);
1811
+ 'general liability', 'commercial property', 'workers compensation', 'excess casualty', 'inland marine', 'ocean marine', 'commercial auto', 'first notice of loss', 'first notice', 'incident report', 'incurred losses', 'case reserve', 'loss run', 'loss history', 'subrogation claim', 'salvage recovery', 'treaty cession', 'reinsurance treaty', 'retention limit', 'public adjuster', 'independent adjuster', 'lead underwriter', 'producing agent', 'additional insured', 'named insured', 'injured worker', 'tpa examiner',
1812
+ 'são paulo', 'sao paulo', 'rio de janeiro', 'belo horizonte', 'brasília', 'brasilia', 'curitiba', 'porto alegre', 'salvador', 'fortaleza', 'recife', 'campinas', 'goiânia', 'goiania', 'manaus', 'belém', 'belem', 'florianópolis', 'florianopolis', 'vitória', 'vitoria', 'natal', 'maceió', 'maceio', 'joão pessoa', 'joao pessoa', 'prontuário médico', 'prontuario medico', 'evolução clínica', 'evolucao clinica', 'alta hospitalar', 'exame oftalmológico', 'exame oftalmologico', 'acuidade visual', 'pressão intraocular', 'pressao intraocular', 'fundo de olho', 'biomicroscopia', 'receita médica', 'receita medica', 'laudo médico', 'laudo medico', 'termo de consentimento']);
1709
1813
 
1710
1814
  let JARGON_WORDS = new Set(['step', 'page', 'grade', 'version', 'course', 'class', 'follow', 'chapter', 'lesson', 'unit', 'marketing', 'manager', 'specialist', 'science', 'administration', 'university', 'skills', 'leadership', 'communication', 'working', 'proficiency', 'decision', 'driven', 'experience', 'summary', 'bachelor', 'ads', 'solutions', 'positioning', 'acquisition', 'strategy', 'research', 'database', 'forecast', 'interest', 'prepared', 'merchant', 'document', 'feedback', 'template', 'campaign', 'partners', 'settings', 'keystone', 'llm', 'gpt', 'chatgpt', 'openai', 'anthropic', 'claude', 'gemini', 'api', 'json', 'xml', 'html', 'css', 'javascript', 'python', 'golang', 'typescript', 'rust', 'fastapi', 'snowflake', 'kubernetes', 'terraform', 'docker', 'redis', 'kafka', 'pytorch', 'policy', 'terms', 'conditions', 'release', 'sprint', 'deployment', 'cluster', 'instance', 'package', 'module', 'revenue', 'margin', 'gross', 'quarter', 'system', 'code', 'data', 'cloud', 'server', 'database', 'artificial', 'intelligence', 'learning', 'generative', 'regular', 'hours', 'holiday', 'earnings', 'deductions', 'withheld', 'withholding', 'taxes', 'medicare', 'benefits', 'reimbursements', 'compensation', 'wages', 'code', 'uk', 'eu', 'us', 'usa', 'dpa', 'ico', 'dpo', 'ciso', 'ssrn', 'elsevier', 'gdpr', 'ccpa', 'cpra', 'hipaa', 'soc2', 'iso27001', 'governance', 'regulatory', 'framework', 'mandate', 'mandates', 'guidance', 'statute', 'statutes', 'jurisdiction', 'jurisdictions', 'recital', 'article', 'articles', 'treatise', 'workstation', 'device', 'endpoint', 'blueprint', 'personal', 'cross-border', 'cross', 'border', 'transfer', 'retrieval', 'augmented', 'generation', 'limitation', 'discrimination', 'imposes', 'heavy', 'identifiable', 'bypasses', 'author', 'authors', 'contact', 'contacts', 'keywords', 'keyword', 'keys', 'key', 'separation', 'pre-print', 'preprint', 'abstract', 'structured', 'background', 'methods', 'results', 'discussion', 'conclusion', 'conclusions', 'introduction', 'overview', 'workflow', 'paradigm', 'nomenclature', 'repository', 'classification', 'phenotypes', 'biomedical', 'bioinformatics', 'identifiers', 'de-identification', 'sanitization', 'ephemeral', 'validation', 'validating', 'genomic', 'genomics', 'clinical', 'phenotype', 'target', 'corresponding', 'subject', 'biorxiv', 'medrxiv', 'arxiv', 'privacyscrubber', 'research', 'lead', 'architect', 'chief', 'founder', 'symbols', 'gene', 'genes', 'sample', 'barcode', 'pipeline', 'downstream', 'pedigree', 'trees', 'exposes', 'violates', 'protecting', 'anonymization', 'redacted', 'redaction', 'zero-trust', 'ztds',
1711
1815
  'conventional', 'model', 'models', 'potential', 'multi', 'million', 'dollar', 'dollars', 'surrogate', 'surrogates', 'geographic', 'subdivisions', 'subdivision', 'inference', 'response', 'responses', 'latency', 'benchmarks', 'benchmark', 'experimental', 'methodology', 'informatics', 'ethics', 'accountability', 'civil', 'rights', 'perimeter', 'network', 'requirements', 'requirement', 'statutory', 'categories', 'category', 'regulations', 'regulation', 'penalties', 'penalty', 'disclosure', 'disclosures', 'clinician', 'terminal', 'transport', 'inpatient', 'discharge', 'summaries', 'operative', 'reports', 'outpatient', 'psychiatric', 'telehealth', 'consultation', 'pathology', 'sequencing', 'laboratory', 'sensitivity', 'specificity', 'proxy', 'gateways', 'gateway', 'packet', 'packets', 'inspection', 'declarations', 'availability', 'competing', 'interests', 'interventions', 'multicenter', 'multi-center', 'institution', 'institutional', 'protocol', 'deviations', 'adverse', 'events', 'abbreviations', 'agents', 'agent', 'committee', 'committees', 'board', 'boards', 'nih', 'pubmed', 'central', 'open', 'access', 'policy', 'trial', 'trials', 'crf', 'crfs', 'ehr', 'ehrs', 'baa', 'baas', 'hitech', 'safe', 'harbor', 'section',
@@ -1717,7 +1821,7 @@ const US_STATES_SET = new Set([
1717
1821
  ]);
1718
1822
 
1719
1823
  const CORPORATE_SUFFIXES = new Set([
1720
- 'inc', 'llc', 'corp', 'corporation', 'ltd', 'limited', 'co', 'company',
1824
+ 'inc', 'llc', 'corp', 'corporation', 'ltd', 'limited', 'co', 'company', 'ltda', 'eireli', 'sa', 's/a',
1721
1825
  'group', 'holdings', 'solutions', 'services', 'servicing', 'technologies', 'logistics',
1722
1826
  'industries', 'capital', 'bank', 'partners', 'llp', 'pllc', 'usa',
1723
1827
  'gmbh', 'ag', 'ohg', 'kg', 'ug', 'ev', 'sp', 'zoo', 'oo', 'automotive',
@@ -1746,7 +1850,7 @@ let NOT_NAME_WORDS = new Set([
1746
1850
  'substation', 'feeder', 'transformer', 'recloser', 'inverter', 'turbine', 'synchrophasor', 'scada', 'ics', 'dcs', 'plc', 'rtu', 'ied', 'pmu', 'ami', 'bess', 'derms', 'adms', 'hvdc', 'facts', 'avr', 'agc', 'lmp', 'ppa', 'nerc cip', 'cip-002', 'cip-014', 'ferc', 'pjm', 'miso', 'caiso', 'ercot', 'nyiso', 'iso-ne', 'spp', 'entso-e', 'wecc', 'serc', 'rfc', 'tre', 'mro', 'npcc', 'dnp3', 'modbus', 'opc ua', 'openadr', 'active power', 'reactive power', 'apparent power', 'power factor', 'peak demand', 'curtailment', 'bulk electric system', 'critical infrastructure', 'smart grid', 'smart meter',
1747
1851
  'utility', 'utilities', 'electric', 'electrical', 'meter', 'metering', 'service point', 'delivery point', 'premise', 'premises', 'programmable', 'logic', 'telemetry', 'solar', 'plant', 'wind', 'storage', 'battery', 'bulk', 'ua', 'mqtt', 'sparkplug',
1748
1852
  'vehicle', 'primary', 'secondary', 'asset', 'fleet', 'engine', 'controller', 'dealer', 'freeze', 'frame', 'work order', 'repair order', 'sedan', 'suv', 'iso 26262', 'iso 21434', 'sae j1939', 'sae j1772', 'sae j1979', 'iso 15118', 'iso 14229', 'ocpp', 'can bus', 'can fd', 'flexray', 'some/ip', 'doip', 'diagnostic trouble codes', 'diagnostic', 'trouble', 'codes', 'dtc', 'obd', 'obd-ii', 'ecu', 'bcm', 'tcu', 'pcm', 'vcu', 'bms', 'telematics', 'odometer', 'powertrain', 'inverter', 'alternator', 'transponder', 'evse', 'dcfc', 'ccs1', 'ccs2', 'nacs', 'chademo', 'state of charge', 'state of health', 'soc', 'soh', 'adas', 'lidar', 'radar', 'asil', 'asil-d', 'nhtsa', 'fmvss', 'fmcsa', 'wltp', 'carb', 'p0300', 'p0420', 'u0100', 'b0001', 'c0035',
1749
- 'sec', 'cftc', 'finra', 'fdic', 'occ', 'cfpb', 'fedwire', 'reserve', 'federal', 'law', 'corporation', 'delaware', 'dgcl',
1853
+ 'sec', 'cftc', 'finra', 'fdic', 'occ', 'cfpb', 'fedwire', 'reserve', 'federal', 'law', 'corporation', 'delaware', 'dgcl', 'group', 'holdings', 'limited', 'inc', 'llc', 'corp', 'co',
1750
1854
  'lms', 'edtech', 'canvas', 'blackboard', 'moodle', 'schoology', 'brightspace', 'proctorio', 'honorlock', 'respondus', 'proctoru', 'coppa', 'idea', 'section 504', 'clery', 'clery act', 'title iv', 'sai', 'student aid index', 'student aid report', 'bip', 'lep', 'ell', 'esl', 'sevis', 'form i-20', 'ds-2019', 'sat', 'act', 'gre', 'gmat', 'mcat', 'lsat', 'toefl', 'ielts', 'abet', 'wasc', 'sacscoc', 'hlc', 'neche', 'valedictorian', 'salutatorian', 'cum laude', 'magna cum laude', 'summa cum laude', 'ed.d', 'edd', 'j.d.', 'student', 'learner', 'conduct', 'behavioral', 'intervention', 'english', 'proficiency', 'accommodations', 'accommodation', 'proctoring',
1751
1855
  '3gpp', 'etsi', 'itu-t', 'ietf', 'rfc', 'sip', 'sdp', 'rtp', 'rtcp', 'srtp', 'stun', 'turn', 'ice', 'webrtc', 'ss7', 'sigtran', 'diameter', 'radius', 'docsis', 'gpon', 'xgs-pon', 'dwdm', 'cwdm', 'otn', 'mpls', 'bgp', 'ospf', 'is-is', 'ebgp', 'ibgp', 'qos', 'diffserv', 'volte', 'vonr', 'vowifi', 'esim', 'imsi', 'imei', 'imeisv', 'iccid', 'msisdn', 'enodeb', 'gnodeb', 'nodeb', 'cell id', 'pci', 'tac', 'lac', 'cgi', 'ecgi', 'tr-069', 'tr-369', 'cwmp', 'g.711', 'g.729', 'g.722', 'opus', 'amr-wb', 'evs', 'mos', 'jitter', 'packet loss', 'rtt', 'bitrate', 'c-band', 'mmwave', 'sub-6', 'telecom', 'carrier', 'backhaul', 'fronthaul', 'peering', 'circuit id', 'facility id',
1752
1856
  'caller', 'evaluator', 'diarization', 'transcript', 'transcription', 'scorecard', 'ivr', 'dtmf', 'ani', 'cli', 'dnis', 'aht', 'asa', 'fcr', 'csat', 'nps', 'wrap-up', 'wrapup', 'genesys', 'five9', 'nice', 'incontact', 'talkdesk', 'ucid', 'telephony',
@@ -2310,15 +2414,15 @@ const PROFILE_JARGON = {
2310
2414
  const PROFILE_ALIAS_MAP = {
2311
2415
  'general': 'general',
2312
2416
  'underwriting': 'underwriting', 'lending': 'underwriting', 'mortgage': 'underwriting', 'loan': 'underwriting', 'income': 'underwriting', 'income_verification': 'underwriting', 'payroll': 'underwriting', 'w2': 'underwriting', 'paystub': 'underwriting',
2313
- 'medical': 'medical', 'healthcare': 'medical', 'health': 'medical', 'pharma': 'pharma', 'clinical': 'pharma', 'trials': 'pharma', 'lifesciences': 'pharma', 'cro': 'pharma', 'pharmacovigilance': 'pharma', 'hipaa': 'medical',
2417
+ 'medical': 'medical', 'healthcare': 'medical', 'health': 'medical', 'doctor': 'medical', 'physician': 'medical', 'clinic': 'medical', 'hospital': 'medical', 'pharma': 'pharma', 'clinical': 'pharma', 'trials': 'pharma', 'lifesciences': 'pharma', 'cro': 'pharma', 'pharmacovigilance': 'pharma', 'hipaa': 'medical',
2314
2418
  'biotech': 'biotech', 'genomics': 'biotech', 'bioinformatics': 'biotech', 'genetics': 'biotech', 'sequencing': 'biotech', 'dna': 'biotech',
2315
- 'engineering': 'engineering', 'dev': 'dev', 'api': 'dev', 'devops': 'engineering', 'tech': 'tech', 'network': 'tech', 'manufacturing': 'engineering', 'industrial': 'engineering', 'production': 'engineering', 'factory': 'engineering', 'plant': 'engineering',
2419
+ 'finance': 'finance', 'financial': 'finance', 'fintech': 'finance', 'banking': 'finance', 'bank': 'finance', 'payments': 'finance', 'bizops': 'bizops', 'logistics': 'bizops', 'supplychain': 'bizops', 'customs': 'bizops', 'freight': 'bizops', 'shipping': 'bizops', 'trade': 'bizops', 'ops': 'bizops', 'erp': 'bizops', 'sales': 'sales', 'procurement': 'sales', 'clm': 'sales', 'contracts': 'sales', 'sourcing': 'sales', 'wealthmgmt': 'wealthmgmt', 'wealth': 'wealthmgmt', 'insurance': 'insurance', 'accounting': 'accounting', 'tax': 'accounting', 'taxes': 'accounting', 'invoicing': 'accounting', 'invoice': 'accounting', 'ap': 'accounting', 'ar': 'accounting', 'ledger': 'accounting', 'pci': 'finance',
2420
+ 'legal': 'legal', 'law': 'legal', 'attorney': 'legal', 'litigation': 'legal', 'compliance': 'compliance', 'defense': 'compliance', 'gov': 'compliance', 'government': 'compliance', 'dod': 'compliance', 'cmmc': 'compliance', 'cui': 'compliance', 'itar': 'compliance', 'ccpa': 'ccpa', 'gdpr': 'compliance', 'soc2': 'compliance', 'iso27001': 'compliance', 'nist': 'compliance', 'dpo': 'compliance', 'grc': 'compliance',
2421
+ 'engineering': 'engineering', 'dev': 'dev', 'code': 'dev', 'software': 'dev', 'developer': 'dev', 'api': 'dev', 'devops': 'engineering', 'tech': 'tech', 'network': 'tech', 'manufacturing': 'engineering', 'industrial': 'engineering', 'production': 'engineering', 'factory': 'engineering', 'plant': 'engineering',
2316
2422
  'telecom': 'telecom', 'callcenter': 'telecom', 'contactcenter': 'telecom', 'voip': 'telecom', 'telephony': 'telecom', 'ivr': 'telecom', 'telco': 'telecom', 'sip': 'telecom', 'cdr': 'telecom', 'audio': 'telecom',
2317
- 'finance': 'finance', 'financial': 'finance', 'bizops': 'bizops', 'logistics': 'bizops', 'supplychain': 'bizops', 'customs': 'bizops', 'freight': 'bizops', 'shipping': 'bizops', 'trade': 'bizops', 'ops': 'bizops', 'erp': 'bizops', 'sales': 'sales', 'procurement': 'sales', 'clm': 'sales', 'contracts': 'sales', 'sourcing': 'sales', 'wealthmgmt': 'wealthmgmt', 'wealth': 'wealthmgmt', 'insurance': 'insurance', 'accounting': 'accounting', 'pci': 'finance',
2318
- 'legal': 'legal', 'compliance': 'compliance', 'defense': 'compliance', 'gov': 'compliance', 'government': 'compliance', 'dod': 'compliance', 'cmmc': 'compliance', 'cui': 'compliance', 'itar': 'compliance', 'ccpa': 'ccpa', 'gdpr': 'compliance', 'soc2': 'compliance', 'iso27001': 'compliance', 'nist': 'compliance', 'dpo': 'compliance', 'grc': 'compliance',
2319
- 'hr': 'hr', 'people': 'hr', 'talent': 'hr', 'mobility': 'hr', 'recruiting': 'hr', 'hcm': 'hr', 'compensation': 'hr', 'security': 'security', 'marketing': 'marketing', 'support': 'support',
2423
+ 'agents': 'agents', 'ai_agents': 'agents', 'agent': 'agents', 'langchain': 'agents', 'llamaindex': 'agents', 'mcp': 'agents', 'vector': 'agents', 'rag': 'agents',
2424
+ 'hr': 'hr', 'people': 'hr', 'talent': 'hr', 'mobility': 'hr', 'recruiting': 'hr', 'hcm': 'hr', 'compensation': 'hr', 'security': 'security', 'cybersec': 'security', 'cybersecurity': 'security', 'infosec': 'security', 'secops': 'security', 'marketing': 'marketing', 'support': 'support',
2320
2425
  'realestate': 'realestate', 'realty': 'realestate', 'property': 'realestate', 'title': 'realestate', 'escrow': 'realestate', 'hoa': 'realestate', 'academic': 'academic', 'education': 'academic', 'edu': 'academic', 'student': 'academic', 'highered': 'academic', 'edtech': 'academic', 'lms': 'academic',
2321
- 'agents': 'agents', 'ai_agents': 'agents', 'agent': 'agents', 'langchain': 'agents', 'llamaindex': 'agents', 'mcp': 'agents', 'vector': 'agents',
2322
2426
  'creative': 'creative',
2323
2427
  'personal': 'personal', 'vip': 'personal', 'executive': 'personal', 'protection': 'personal', 'familyoffice': 'personal', 'family_office': 'personal',
2324
2428
  'automotive': 'automotive', 'auto': 'automotive', 'vehicle': 'automotive', 'telematics': 'automotive', 'ev': 'automotive', 'fleet': 'automotive',
@@ -2363,7 +2467,7 @@ const PROFILE_JARGON = {
2363
2467
  // Israel Teudat Zehut & Corporate ID (H.P. / 50-58 prefixes): 8 or 9 digits, Luhn check (Mod 10)
2364
2468
  if (digits.length < 8 || digits.length > 9) return false;
2365
2469
  const prefix = fullText.substring(Math.max(0, start - 40), start);
2366
- const hasContext = /(?:תעודת\s+זהות|ת\.?ז\.?|מספר\s+זהות|Teudat\s+Zehut|TZ|תעודה|ח\.?פ\.?|ע\.?ר\.?|ע\.?מ\.?|מספר\s+תאגיד|חברה\s+פרטית|עוסק\s+מורשה|חברה)/i.test(prefix);
2470
+ const hasContext = /(?:תעודת\s+זהות|ת\.?ז\.?|ת["״]ז|מספר\s+זהות|מ\.?ז\.?|Teudat\s+Zehut|TZ|תעודה|ח\.?פ\.?|ח["״]פ|ע\.?ר\.?|ע\.?מ\.?|ע["״]מ|מע["״]מ|מספר\s+תאגיד|חברה\s+פרטית|עוסק\s+מורשה|חברה)/i.test(prefix);
2367
2471
  const idStr = digits.padStart(9, '0');
2368
2472
  let sum = 0;
2369
2473
  for (let i = 0; i < 9; i++) {
@@ -2396,6 +2500,10 @@ const PROFILE_JARGON = {
2396
2500
  // UK NHS Number: 10 digits, Mod 11 check
2397
2501
  if (digits.length !== 10) return false;
2398
2502
  const hasContext = /NHS/i.test(prefix);
2503
+ if (!hasContext) {
2504
+ if (/^\d{3}-\d{3}-\d{4}$/.test(val.trim())) return false;
2505
+ if (/(?:phone|tel|call|mobile|cell|fax|contact|sms|whatsapp|נייד|טלפון)/i.test(prefix)) return false;
2506
+ }
2399
2507
  const weights = [10, 9, 8, 7, 6, 5, 4, 3, 2];
2400
2508
  let sum = 0;
2401
2509
  for (let i = 0; i < 9; i++) sum += Number(digits[i]) * weights[i];
@@ -2567,13 +2675,229 @@ const PROFILE_JARGON = {
2567
2675
  return hasContext ? (valid || true) : valid;
2568
2676
  }
2569
2677
 
2678
+ case 'mod11_br_cpf': {
2679
+ const clean = val.replace(/\D/g, '');
2680
+ if (clean.length !== 11 || /^(\d)\1{10}$/.test(clean)) return false;
2681
+ const prefix = fullText.substring(Math.max(0, start - 35), start);
2682
+ const hasContext = /\b(?:CPF|Cadastro\s+de\s+Pessoas?\s+F[íi]sicas?)\b/i.test(prefix);
2683
+ const isPunctuated = /^\d{3}\.\d{3}\.\d{3}-\d{2}$/.test(val.trim());
2684
+ let s1 = 0, s2 = 0;
2685
+ for (let i = 0; i < 9; i++) {
2686
+ const d = clean.charCodeAt(i) - 48;
2687
+ s1 += d * (10 - i);
2688
+ s2 += d * (11 - i);
2689
+ }
2690
+ const d1 = (s1 * 10) % 11 === 10 ? 0 : (s1 * 10) % 11;
2691
+ s2 += d1 * 2;
2692
+ const d2 = (s2 * 10) % 11 === 10 ? 0 : (s2 * 10) % 11;
2693
+ const valid = (d1 === (clean.charCodeAt(9) - 48) && d2 === (clean.charCodeAt(10) - 48));
2694
+ return (hasContext || isPunctuated) ? true : valid;
2695
+ }
2696
+
2697
+ case 'mod11_br_cnpj': {
2698
+ const clean = val.replace(/\D/g, '');
2699
+ if (clean.length !== 14 || /^(\d)\1{13}$/.test(clean)) return false;
2700
+ const prefix = fullText.substring(Math.max(0, start - 35), start);
2701
+ const hasContext = /\b(?:CNPJ|Cadastro\s+Nacional\s+da\s+Pessoa\s+Jur[íi]dica)\b/i.test(prefix);
2702
+ const isPunctuated = /^\d{2}\.\d{3}\.\d{3}\/\d{4}-\d{2}$/.test(val.trim());
2703
+ const w1 = [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
2704
+ const w2 = [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2];
2705
+ let s1 = 0, s2 = 0;
2706
+ for (let i = 0; i < 12; i++) {
2707
+ const d = clean.charCodeAt(i) - 48;
2708
+ s1 += d * w1[i];
2709
+ s2 += d * w2[i];
2710
+ }
2711
+ const r1 = s1 % 11;
2712
+ const d1 = r1 < 2 ? 0 : 11 - r1;
2713
+ s2 += d1 * w2[12];
2714
+ const r2 = s2 % 11;
2715
+ const d2 = r2 < 2 ? 0 : 11 - r2;
2716
+ const valid = (d1 === (clean.charCodeAt(12) - 48) && d2 === (clean.charCodeAt(13) - 48));
2717
+ return (hasContext || isPunctuated) ? true : valid;
2718
+ }
2719
+
2720
+ case 'aadhaar_in': {
2721
+ const clean = val.replace(/\D/g, '');
2722
+ if (clean.length !== 12 || /^[01]/.test(clean) || /^(\d)\1{11}$/.test(clean)) return false;
2723
+ return true;
2724
+ }
2725
+
2726
+ case 'mod10_pl_pesel': {
2727
+ const clean = val.replace(/\D/g, '');
2728
+ if (clean.length !== 11 || /^(\d)\1{10}$/.test(clean)) return false;
2729
+ const prefix = fullText.substring(Math.max(0, start - 30), start);
2730
+ const hasContext = /\bPESEL\b/i.test(prefix);
2731
+ const weights = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
2732
+ let sum = 0;
2733
+ for (let i = 0; i < 10; i++) sum += (clean.charCodeAt(i) - 48) * weights[i];
2734
+ const check = (10 - (sum % 10)) % 10;
2735
+ const valid = (check === (clean.charCodeAt(10) - 48));
2736
+ return hasContext ? true : valid;
2737
+ }
2738
+
2739
+ case 'mod11_pl_nip': {
2740
+ const clean = val.replace(/\D/g, '');
2741
+ if (clean.length !== 10 || /^(\d)\1{9}$/.test(clean)) return false;
2742
+ const prefix = fullText.substring(Math.max(0, start - 30), start);
2743
+ const hasContext = /\bNIP\b/i.test(prefix);
2744
+ const weights = [6, 5, 7, 2, 3, 4, 5, 6, 7];
2745
+ let sum = 0;
2746
+ for (let i = 0; i < 9; i++) sum += (clean.charCodeAt(i) - 48) * weights[i];
2747
+ const check = sum % 11;
2748
+ if (check === 10 && !hasContext) return false;
2749
+ const valid = (check === (clean.charCodeAt(9) - 48));
2750
+ return hasContext ? true : valid;
2751
+ }
2752
+
2753
+ case 'mod11_ar_cuit': {
2754
+ const clean = val.replace(/\D/g, '');
2755
+ if (clean.length !== 11) return false;
2756
+ const prefix = fullText.substring(Math.max(0, start - 30), start);
2757
+ const hasContext = /\bCUI[TL]\b/i.test(prefix);
2758
+ const isPunctuated = /^(?:20|23|24|27|30|33|34)-\d{8}-\d$/.test(val.trim());
2759
+ const weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
2760
+ let sum = 0;
2761
+ for (let i = 0; i < 10; i++) sum += (clean.charCodeAt(i) - 48) * weights[i];
2762
+ let check = 11 - (sum % 11);
2763
+ if (check === 11) check = 0;
2764
+ else if (check === 10) check = 9;
2765
+ const valid = (check === (clean.charCodeAt(10) - 48));
2766
+ return (hasContext || isPunctuated) ? true : valid;
2767
+ }
2768
+
2769
+ case 'luhn_se_personnummer': {
2770
+ let clean = val.replace(/\D/g, '');
2771
+ if (clean.length === 12) clean = clean.slice(2);
2772
+ if (clean.length !== 10 || /^(\d)\1{9}$/.test(clean)) return false;
2773
+ const prefix = fullText.substring(Math.max(0, start - 35), start);
2774
+ const hasContext = /\b(?:Personnummer|Samordningsnummer|Pnr)\b/i.test(prefix);
2775
+ let sum = 0;
2776
+ for (let i = 0; i < 10; i++) {
2777
+ let inc = (clean.charCodeAt(i) - 48) * ((i % 2 === 0) ? 2 : 1);
2778
+ if (inc > 9) inc -= 9;
2779
+ sum += inc;
2780
+ }
2781
+ const valid = (sum % 10 === 0);
2782
+ return hasContext ? true : valid;
2783
+ }
2784
+
2785
+ case 'ean13_ch_ahv': {
2786
+ const clean = val.replace(/\D/g, '');
2787
+ if (clean.length !== 13 || !clean.startsWith('756')) return false;
2788
+ const prefix = fullText.substring(Math.max(0, start - 35), start);
2789
+ const hasContext = /\b(?:AHV|AVS|OASI)\b/i.test(prefix);
2790
+ const isPunctuated = /^756\.\d{4}\.\d{4}\.\d{2}$/.test(val.trim());
2791
+ let sum = 0;
2792
+ for (let i = 0; i < 12; i++) {
2793
+ sum += (clean.charCodeAt(i) - 48) * ((i % 2 === 0) ? 1 : 3);
2794
+ }
2795
+ const check = (10 - (sum % 10)) % 10;
2796
+ const valid = (check === (clean.charCodeAt(12) - 48));
2797
+ return (hasContext || isPunctuated) ? true : valid;
2798
+ }
2799
+
2800
+ case 'mod11_2_cn_id': {
2801
+ const clean = val.replace(/\s+/g, '').toUpperCase();
2802
+ if (!/^\d{17}[0-9X]$/.test(clean)) return false;
2803
+ const hasContext = /\b(?:Shenfenzheng|CN\s+ID|Resident\s+ID)\b|身份证/iu.test(prefix);
2804
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
2805
+ const checkChars = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
2806
+ let sum = 0;
2807
+ for (let i = 0; i < 17; i++) {
2808
+ sum += (clean.charCodeAt(i) - 48) * weights[i];
2809
+ }
2810
+ const valid = (checkChars[sum % 11] === clean[17]);
2811
+ return hasContext ? true : valid;
2812
+ }
2813
+
2814
+ case 'mod10_tw_id': {
2815
+ const clean = val.replace(/\s+/g, '').toUpperCase();
2816
+ if (!/^[A-Z][1289]\d{8}$/.test(clean)) return false;
2817
+ const hasContext = /\b(?:TW\s+ID|Taiwan\s+ID|ROC\s+ID)\b|身分證/iu.test(prefix);
2818
+ const letterMap = {
2819
+ A: 10, B: 11, C: 12, D: 13, E: 14, F: 15, G: 16, H: 17, I: 34,
2820
+ J: 18, K: 19, L: 20, M: 21, N: 22, O: 35, P: 23, Q: 24, R: 25,
2821
+ S: 26, T: 27, U: 28, V: 29, W: 32, X: 30, Y: 31, Z: 33
2822
+ };
2823
+ const code = letterMap[clean[0]];
2824
+ if (!code) return false;
2825
+ const weights = [8, 7, 6, 5, 4, 3, 2, 1, 1];
2826
+ let sum = Math.floor(code / 10) + (code % 10) * 9;
2827
+ for (let i = 0; i < 9; i++) {
2828
+ sum += (clean.charCodeAt(i + 1) - 48) * weights[i];
2829
+ }
2830
+ const valid = (sum % 10 === 0);
2831
+ return hasContext ? true : valid;
2832
+ }
2833
+
2834
+ case 'mod11_kr_rrn': {
2835
+ const clean = val.replace(/\D/g, '');
2836
+ if (clean.length !== 13) return false;
2837
+ const hasContext = /\b(?:RRN|Jumin)\b/i.test(prefix);
2838
+ const isHyphenated = /^\d{6}-\d{7}$/.test(val.trim());
2839
+ const weights = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];
2840
+ let sum = 0;
2841
+ for (let i = 0; i < 12; i++) {
2842
+ sum += (clean.charCodeAt(i) - 48) * weights[i];
2843
+ }
2844
+ const check = (11 - (sum % 11)) % 10;
2845
+ const valid = (check === (clean.charCodeAt(12) - 48));
2846
+ return (hasContext || isHyphenated) ? true : valid;
2847
+ }
2848
+
2849
+ case 'mod11_pt_nif': {
2850
+ const clean = val.replace(/\D/g, '');
2851
+ if (clean.length !== 9) return false;
2852
+ const hasContext = /\b(?:PT\s+NIF|NIF|NIPC|Contribuinte)\b/i.test(prefix);
2853
+ let sum = 0;
2854
+ for (let i = 0; i < 8; i++) {
2855
+ sum += (clean.charCodeAt(i) - 48) * (9 - i);
2856
+ }
2857
+ const rem = sum % 11;
2858
+ const check = rem < 2 ? 0 : 11 - rem;
2859
+ const valid = (check === (clean.charCodeAt(8) - 48));
2860
+ return hasContext ? true : valid;
2861
+ }
2862
+
2570
2863
  default:
2571
2864
  return true;
2572
2865
  }
2573
2866
  }
2574
2867
 
2868
+ const HOMOGLYPH_EMAIL_MAP = {
2869
+ '\u0430': 'a', '\u0410': 'A', '\u0435': 'e', '\u0415': 'E',
2870
+ '\u043E': 'o', '\u041E': 'O', '\u0440': 'p', '\u0420': 'P',
2871
+ '\u0441': 'c', '\u0421': 'C', '\u0443': 'y', '\u0423': 'Y',
2872
+ '\u0445': 'x', '\u0425': 'X', '\u0456': 'i', '\u0406': 'I',
2873
+ '\u0458': 'j', '\u0408': 'J', '\u04CF': 'l', '\u04C0': 'I',
2874
+ '\u03B1': 'a', '\u03BF': 'o', '\u03C1': 'p', '\u03B5': 'e',
2875
+ '\u03BA': 'k', '\u03BD': 'v', '\u03C4': 't', '\u03C5': 'u'
2876
+ };
2877
+
2878
+ function normalizeEnclosedDigit(ch) {
2879
+ const code = ch.charCodeAt(0);
2880
+ if (code === 0x24EA || code === 0x24FF) return '0';
2881
+ if (code >= 0x2460 && code <= 0x2468) return String(code - 0x2460 + 1);
2882
+ if (code >= 0x2776 && code <= 0x277E) return String(code - 0x2776 + 1);
2883
+ if (code >= 0x2780 && code <= 0x2788) return String(code - 0x2780 + 1);
2884
+ if (code >= 0x278A && code <= 0x2792) return String(code - 0x278A + 1);
2885
+ return ch;
2886
+ }
2887
+
2575
2888
  function detectMatches(text, activeProfile = 'general', customRules = [], nlpNames = [], enabledEntities = null, mlEntities = []) {
2576
- let textToProcess = stitchOrphanedNameLines(text, activeProfile).replace(/[\u200b\u200c\u200d\ufeff\u00ad]/gi, '');
2889
+ const normalizedInput = typeof text === 'string' && typeof text.normalize === 'function' ? text.normalize('NFC') : String(text || '');
2890
+ let textToProcess = stitchOrphanedNameLines(normalizedInput, activeProfile)
2891
+ .replace(/[\u034f\u180e\u200b-\u200f\u202a-\u202e\u2060-\u2069\ufeff\u00ad]/gi, '')
2892
+ .replace(/[\uFF0B\uFF0D-\uFF1A\uFF20-\uFF3A\uFF3F\uFF41-\uFF5A]/g, ch => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0))
2893
+ .replace(/[\u{1D400}-\u{1D7FF}]/gu, ch => ch.normalize('NFKD'))
2894
+ .replace(/[\u2460-\u2468\u24EA\u24FF\u2776-\u277E\u2780-\u2788\u278A-\u2792]/g, normalizeEnclosedDigit)
2895
+ .replace(/[^\s@<>,"']+@[^\s@<>,"']+\.[^\s@<>,"']+/g, token => {
2896
+ if (/[a-zA-Z0-9]/.test(token) && /[\u0400-\u04FF\u0370-\u03FF]/.test(token)) {
2897
+ return token.replace(/[\u0400-\u04FF\u0370-\u03FF]/g, ch => HOMOGLYPH_EMAIL_MAP[ch] || ch);
2898
+ }
2899
+ return token;
2900
+ });
2577
2901
  textToProcess = textToProcess.replace(/([a-z])(Email|Phone|Mobile|Tel|Address|IP|ID|URL|SSN|Date):/g, '$1 $2:');
2578
2902
 
2579
2903
  let matches = [];
@@ -2649,9 +2973,9 @@ const PROFILE_JARGON = {
2649
2973
  matches.push({ start, end, value: matchedText, type: rule.type });
2650
2974
  } else {
2651
2975
  if (rule.type === 'NAME') {
2652
- matchedText = matchedText.replace(/[,;:]+$/, '').trim();
2976
+ matchedText = matchedText.replace(/[,;:\s]+$/, '').trim();
2653
2977
  if (!/\b[A-Za-z]\.$/.test(matchedText) && !/\b(?:Jr|Sr)\.$/.test(matchedText)) {
2654
- matchedText = matchedText.replace(/\.+$/, '').trim();
2978
+ matchedText = matchedText.replace(/[.\s]+$/, '').trim();
2655
2979
  }
2656
2980
  }
2657
2981
  let val = matchedText.toLowerCase().trim();
@@ -2808,16 +3132,16 @@ const PROFILE_JARGON = {
2808
3132
  }
2809
3133
 
2810
3134
  const LABEL_ALIASES = {
2811
- NAME: ['NAME', 'NAMES', 'USERNAME', 'USER_NAME', 'USER', 'USERS', 'CLIENTNAME', 'CLIENT_NAME', 'CANDIDATE_NAME', 'FULL_NAME', 'FIRSTNAME', 'FIRST_NAME', 'LASTNAME', 'LAST_NAME', 'SURNAME', 'ИМЯ', 'ИМЕНА', 'ПОЛЬЗОВАТЕЛЬ', 'ФИО', 'КЛИЕНТ', 'NOMBRE', 'NOMBRES', 'USUARIO', 'CLIENTE', 'NOM', 'NOMS', 'UTILISATEUR', 'NAME', 'NAMEN', 'BENUTZER', 'KUNDE', 'NOME', 'COGNOME', 'UTENTE', 'NAAM', 'GEBRUIKER', 'KLANT'],
2812
- EMAIL: ['EMAIL', 'EMAILS', 'EMAILADDR', 'EMAIL_ADDR', 'EMAILADDRESS', 'EMAIL_ADDRESS', 'EMAIL_ADR', 'MAIL', 'MAILS', 'ПОЧТА', 'ЭЛ_ПОЧТА', 'АДРЕС_ПОЧТЫ', 'МЕЙЛ', 'МАЙЛ', 'CORREO', 'COURRIEL', 'CORREO_ELECTRONICO', 'MEL'],
2813
- PHONE: ['PHONE', 'PHONES', 'PHONENUM', 'PHONE_NUM', 'PHONENUMBER', 'PHONE_NUMBER', 'TEL', 'TELS', 'TELEPHONE', 'TELEPHONES', 'MOBILE', 'CELL', 'ТЕЛЕФОН', 'ТЕЛЕФОНЫ', 'НОМЕР_ТЕЛЕФОНА', 'НОМЕР', 'MOVIL', 'PORTABLE', 'HANDY', 'TELEFONI', 'CELLULARE'],
2814
- ID: ['ID', 'IDS', 'IDNUM', 'ID_NUM', 'IDNUMBER', 'ID_NUMBER', 'IDENTIFIER', 'IDENTIFIERS', 'ARN', 'ARNS', 'PASSPORT', 'SSN', 'EIN', 'TAXID', 'TAX_ID', 'LICENSE', 'LICENSE_PLATE', 'DEMOGRAPHIC', 'DEMOGRAPHICS', 'ИД', 'ИДЕНТИФИКАТОР', 'ПАСПОРТ', 'СНИЛС', 'ИНН', 'IDENTIFICADOR', 'PASAPORTE', 'IDENTIFIANT', 'PASSEPORT', 'IDENTIFIKATOR', 'PASS', 'IDENTIFICATORE', 'PASSAPORTO'],
2815
- FINANCIAL: ['FINANCIAL', 'FINANCIALS', 'MONEY', 'AMOUNT', 'PRICE', 'COST', 'CARD', 'CREDITCARD', 'DEBITCARD', 'ACCOUNT', 'IBAN', 'BIC', 'ДЕНЬГИ', 'СУММА', 'КАРТА', 'СЧЕТ', 'БАНК', 'DINERO', 'CANTIDAD', 'TARJETA', 'CUENTA', 'ARGENT', 'MONTANT', 'COMPTE', 'GELD', 'BETRAG', 'KONTO'],
2816
- ADDRESS: ['ADDRESS', 'ADDRESSES', 'STREET', 'STREET_ADDRESS', 'CITY', 'STATE', 'ZIP', 'ZIPCODE', 'ZIP_CODE', 'COUNTRY', 'LOCATION', 'АДРЕС', 'АДРЕСА', 'УЛИЦА', 'ГОРОД', 'СТРАНА', 'DIRECCION', 'DIRECCIONES', 'CALLE', 'CIUDAD', 'PAIS', 'ADRESSE', 'ADRESSES', 'RUE', 'VILLE', 'STRASSE', 'STADT', 'LAND'],
2817
- DATE: ['DATE', 'DATES', 'BIRTHDAY', 'DOB', 'ДАТА', 'ДАТЫ', 'ДЕНЬ_РОЖДЕНИЯ', 'FECHA', 'FECHAS', 'CUMPLEANOS', 'ANNIVERSAIRE', 'DATUM', 'DATEN', 'GEBURTSTAG'],
2818
- PHI: ['PHI', 'MRN', 'NHS', 'HEALTH', 'MEDICAL', 'PATIENT', 'МЕД', 'ПАЦИЕНТ', 'PACIENTE'],
2819
- SECRET: ['SECRET', 'SECRETS', 'KEY', 'KEYS', 'TOKEN', 'TOKENS', 'PASSWORD', 'PASSWORDS', 'AUTH', 'APIKEY', 'API_KEY', 'КЛЮЧ', 'КЛЮЧИ', 'ПАРОЛЬ', 'ПАРОЛИ', 'ТОКЕН', 'CLAVE', 'CONTRASENA', 'CLE', 'MOT_DE_PASSE', 'SCHLUESSEL', 'PASSWORT'],
2820
- CUSTOM: ['CUSTOM', 'CUSTOMS', 'RULE', 'RULES', 'КАСТОМ', 'ПРАВИЛО']
3135
+ NAME: ['NAME', 'NAMES', 'USERNAME', 'USER_NAME', 'USER', 'USERS', 'CLIENTNAME', 'CLIENT_NAME', 'CANDIDATE_NAME', 'FULL_NAME', 'FIRSTNAME', 'FIRST_NAME', 'LASTNAME', 'LAST_NAME', 'SURNAME', 'PATIENT', 'PACIENTE', 'DOUTOR', 'MEDICO', 'MÉDICO', 'PRENOM', 'PRÉNOM', 'ИМЯ', 'ИМЕНА', 'ПОЛЬЗОВАТЕЛЬ', 'ФИО', 'КЛИЕНТ', 'NOMBRE', 'NOMBRES', 'USUARIO', 'CLIENTE', 'NOM', 'NOMS', 'UTILISATEUR', 'NAMEN', 'BENUTZER', 'KUNDE', 'NOME', 'NOMES', 'SOBRENOME', 'COGNOME', 'UTENTE', 'NAAM', 'GEBRUIKER', 'KLANT', 'שם', 'שמות', 'משתמש', 'לקוח'],
3136
+ EMAIL: ['EMAIL', 'EMAILS', 'EMAILADDR', 'EMAIL_ADDR', 'EMAILADDRESS', 'EMAIL_ADDRESS', 'EMAIL_ADR', 'MAIL', 'MAILS', 'E-MAIL', 'E_MAIL', 'ПОЧТА', 'ЭЛ_ПОЧТА', 'АДРЕС_ПОЧТЫ', 'МЕЙЛ', 'МАЙЛ', 'CORREO', 'COURRIEL', 'CORREO_ELECTRONICO', 'CORREIO', 'MEL', 'אימייל', 'דואל', 'מייל'],
3137
+ PHONE: ['PHONE', 'PHONES', 'PHONENUM', 'PHONE_NUM', 'PHONENUMBER', 'PHONE_NUMBER', 'TEL', 'TELS', 'TELEPHONE', 'TELEPHONES', 'TÉLÉPHONE', 'TELEFONE', 'CELULAR', 'MOBILE', 'CELL', 'ТЕЛЕФОН', 'ТЕЛЕФОНЫ', 'НОМЕР_ТЕЛЕФОНА', 'НОМЕР', 'MOVIL', 'MÓVIL', 'PORTABLE', 'HANDY', 'TELEFONI', 'CELLULARE', 'טלפון', 'נייד'],
3138
+ ID: ['ID', 'IDS', 'IDNUM', 'ID_NUM', 'IDNUMBER', 'ID_NUMBER', 'IDENTIFIER', 'IDENTIFIERS', 'ARN', 'ARNS', 'PASSPORT', 'SSN', 'EIN', 'TAXID', 'TAX_ID', 'CPF', 'CNPJ', 'RG', 'CRM', 'LICENSE', 'LICENSE_PLATE', 'DEMOGRAPHIC', 'DEMOGRAPHICS', 'NUMERO', 'NUMÉRO', 'NÚMERO', 'ИД', 'ИДЕНТИФИКАТОР', 'ПАСПОРТ', 'СНИЛС', 'ИНН', 'IDENTIFICADOR', 'PASAPORTE', 'IDENTIFIANT', 'PASSEPORT', 'IDENTIFIKATOR', 'PASS', 'IDENTIFICATORE', 'PASSAPORTO', 'PASSAPORTE', 'תז', 'תעודת_זהות', 'מזהה', 'דרכון'],
3139
+ FINANCIAL: ['FINANCIAL', 'FINANCIALS', 'MONEY', 'AMOUNT', 'PRICE', 'COST', 'CARD', 'CREDITCARD', 'DEBITCARD', 'ACCOUNT', 'IBAN', 'BIC', 'VALOR', 'CONTA', 'CARTAO', 'CARTÃO', 'ДЕНЬГИ', 'СУММА', 'КАРТА', 'СЧЕТ', 'БАНК', 'DINERO', 'CANTIDAD', 'TARJETA', 'CUENTA', 'ARGENT', 'MONTANT', 'COMPTE', 'GELD', 'BETRAG', 'KONTO', 'פיננסי', 'סכום', 'חשבון', 'כרטיס'],
3140
+ ADDRESS: ['ADDRESS', 'ADDRESSES', 'STREET', 'STREET_ADDRESS', 'CITY', 'STATE', 'ZIP', 'ZIPCODE', 'ZIP_CODE', 'CEP', 'COUNTRY', 'LOCATION', 'ENDERECO', 'ENDEREÇO', 'RUA', 'CIDADE', 'АДРЕС', 'АДРЕСА', 'УЛИЦА', 'ГОРОД', 'СТРАНА', 'DIRECCION', 'DIRECCIÓN', 'DIRECCIONES', 'CALLE', 'CIUDAD', 'PAIS', 'PAÍS', 'ADRESSE', 'ADRESSES', 'RUE', 'VILLE', 'STRASSE', 'STADT', 'LAND', 'כתובת', 'עיר', 'רחוב'],
3141
+ DATE: ['DATE', 'DATES', 'BIRTHDAY', 'DOB', 'DATA', 'DATAS', 'NASCIMENTO', 'DATA_NASCIMENTO', 'ДАТА', 'ДАТЫ', 'ДЕНЬ_РОЖДЕНИЯ', 'FECHA', 'FECHAS', 'CUMPLEANOS', 'CUMPLEAÑOS', 'ANNIVERSAIRE', 'DATUM', 'DATEN', 'GEBURTSTAG', 'תאריך', 'תאריך_לידה'],
3142
+ PHI: ['PHI', 'MRN', 'NHS', 'CNS', 'SUS', 'PRONTUARIO', 'PRONTUÁRIO', 'HEALTH', 'MEDICAL', 'PATIENT', 'PACIENTE', 'МЕД', 'ПАЦИЕНТ', 'מטופל', 'רפואי'],
3143
+ SECRET: ['SECRET', 'SECRETS', 'KEY', 'KEYS', 'TOKEN', 'TOKENS', 'PASSWORD', 'PASSWORDS', 'AUTH', 'APIKEY', 'API_KEY', 'SENHA', 'SEGREDO', 'CHAVE', 'КЛЮЧ', 'КЛЮЧИ', 'ПАРОЛЬ', 'ПАРОЛИ', 'ТОКЕН', 'CLAVE', 'CONTRASENA', 'CONTRASEÑA', 'CLE', 'CLÉ', 'MOT_DE_PASSE', 'SCHLUESSEL', 'SCHLÜSSEL', 'PASSWORT', 'סוד', 'סיסמה', 'מפתח'],
3144
+ CUSTOM: ['CUSTOM', 'CUSTOMS', 'RULE', 'RULES', 'КАСТОМ', 'ПРАВИЛО', 'מותאם']
2821
3145
  };
2822
3146
 
2823
3147
  function getLabelAliases(label) {
@@ -2875,16 +3199,16 @@ const PROFILE_JARGON = {
2875
3199
  const regexParts = [];
2876
3200
 
2877
3201
  sortedKeys.forEach(k => {
2878
- const inner = k.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '');
3202
+ const inner = k.replace(/^\[|<|\{\{?|\(|__|\]|>|\}\}?|\)|__/g, '');
2879
3203
  const match = inner.match(/^([A-Za-z_0-9]+?)[-_]?(\d+)$/);
2880
3204
  if (match) {
2881
3205
  const label = match[1];
2882
3206
  const baseIndex = parseInt(match[2], 10);
2883
3207
  const aliases = getLabelAliases(label);
2884
3208
 
2885
- const escapedAliases = aliases.map(a => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
3209
+ const escapedAliases = aliases.map(a => a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/[_-]/g, '\\\\?[_-]'));
2886
3210
  const aliasesGroup = `(?:${escapedAliases.join('|')})`;
2887
- const loosePattern = `(?:\\[\\s*${aliasesGroup}[-_\\s]*0*${baseIndex}\\s*\\]|<\\s*${aliasesGroup}[-_\\s]*0*${baseIndex}\\s*>|\\{\\{\\s*${aliasesGroup}[-_\\s]*0*${baseIndex}\\s*\\}\\}|__\\s*${aliasesGroup}[-_\\s]*0*${baseIndex}\\s*__|(?<![A-Za-z0-9\\u0400-\\u04FF_])${aliasesGroup}[-_\\s]*0*${baseIndex}(?![A-Za-z0-9\\u0400-\\u04FF_]))(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})?`;
3211
+ const loosePattern = `(?:(?:\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『)\\s*[*` + '`' + `~]*${aliasesGroup}[-_\\\\\\s]*0*${baseIndex}[*` + '`' + `~]*[.,;:!?]?\\s*(?:\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』)|<\\s*${aliasesGroup}[-_\\\\\\s]*0*${baseIndex}[.,;:!?]?\\s*>|\\{\\{?\\s*${aliasesGroup}[-_\\\\\\s]*0*${baseIndex}[.,;:!?]?\\s*\\}\\}?|__\\s*${aliasesGroup}[-_\\\\\\s]*0*${baseIndex}\\s*__|(?<![A-Za-z0-9\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF_])${aliasesGroup}[-_\\\\\\s]*0*${baseIndex}(?![A-Za-z0-9\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF_]))(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})?`;
2888
3212
  looseRules.push({ token: k, pattern: loosePattern });
2889
3213
  }
2890
3214
  regexParts.push(k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
@@ -2892,7 +3216,7 @@ const PROFILE_JARGON = {
2892
3216
 
2893
3217
  let compositeRegex = null;
2894
3218
  if (regexParts.length > 0) {
2895
- compositeRegex = new RegExp(`(?:\\b|\\[|<|\\{\\{|__)?(?:(?:(?<!\\w)|(?<=\\s))(?:${regexParts.join('|')})(?:(?!\\w)|(?=\\s)))(?:\\b|\\]|>|\\}\\}|__)?(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})?`, 'g');
3219
+ compositeRegex = new RegExp(`(?:\\b|\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『|<|\\{\\{?|__)?(?:(?:(?<!\\w)|(?<=\\s))(?:${regexParts.join('|')})(?:(?!\\w)|(?=\\s)))(?:\\b|\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』|>|\\}\\}?|__)?(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})?`, 'g');
2896
3220
  }
2897
3221
 
2898
3222
  return { compositeRegex, looseRules };
@@ -2918,8 +3242,8 @@ const PROFILE_JARGON = {
2918
3242
  // If text is a full valid JSON object or array, preserve structure
2919
3243
  if (!isJsonPayload(cleaned)) {
2920
3244
  // 1. Strip raw CSS / style blocks leaked from ChatGPT Canvas, web components or stylesheets (handles multi-line, unclosed and variable definitions)
2921
- cleaned = cleaned.replace(/^\s*(?:[.#][a-zA-Z0-9_-]+|\[[a-zA-Z0-9_#.:\-*>=,'"\s]+\]|:is\([^)]+\)|[a-zA-Z0-9_-]+)?\s*\{[^}]*?(?:\}\s*|\n\n+|$)/gi, "");
2922
- cleaned = cleaned.replace(/^[;{} \t\r\n]+/, "");
3245
+ cleaned = cleaned.replace(/^\s*(?:[.#][a-zA-Z0-9_-]+|\[[a-zA-Z0-9_#.:\-*>=,'"\s]+\]|:is\([^)]+\)|[a-zA-Z0-9_-]+)?\s*\{[^}:]*:[^}]*?(?:\}\s*|\n\n+|$)/gi, "");
3246
+ cleaned = cleaned.replace(/^[; \t\r\n]+/, "");
2923
3247
  cleaned = cleaned.replace(/(?:^|\n)[a-zA-Z0-9_#.:\-*>[\]=\s,'"]+\{[^}]*(--[a-zA-Z0-9_-]+:|color-mix\(|var\()[^}]*\}/g, "");
2924
3248
  }
2925
3249
  // 2. Strip AI author prefixes and platform artifacts
@@ -2927,12 +3251,14 @@ const PROFILE_JARGON = {
2927
3251
  cleaned = cleaned.replace(/^(?:Here (?:is|are) (?:the )?(?:redacted|scrubbed|sanitized|processed|clean|updated|modified) (?:text|output|version|data).*?[:\n]+|\*\*Scrubbed Text\*\*[:\n]+|### Scrubbed Text[:\n]+)/i, '');
2928
3252
  cleaned = cleaned.replace(/^\s*Edit\s*\n+/i, "");
2929
3253
  cleaned = cleaned.replace(/\s*\bEdit\s+in\s+a\s+page\b\s*$/i, "");
2930
- // 3. Strip echoed system instructions and PrivacyScrubber pseudonymization disclaimers
3254
+ // 3. Strip echoed system instructions and PrivacyScrubber / ZTDS pseudonymization disclaimers
2931
3255
  cleaned = cleaned.replace(/^\s*(?:Understood|Acknowledged|Got it|Sure|OK|Okay|Note taken|Understood[.,!])[^\n]*\n+/i, "");
2932
- cleaned = cleaned.replace(/(?:^|\n*)\s*\[(?:Privacy Note|Context: identifiers|Privacy Scrubber Mode|SYSTEM INSTRUCTION: DATA PRIVACY MODE)[\s\S]*?exactly as formatted in your response\.?\]\s*(?:\n*|$)/gi, "\n\n");
2933
- cleaned = cleaned.replace(/(?:^|\n*)\s*\[(?:Privacy Note|Context: identifiers|Privacy Scrubber Mode|SYSTEM INSTRUCTION: DATA PRIVACY MODE)[^\]]*\]\s*(?:\n*|$)/gi, "\n\n");
2934
- cleaned = cleaned.replace(/(?:^|\n*)\s*(?:Direct identifiers in this prompt were pseudonymized|I will retain all bracketed tokens)[^\n]*(?:\n*|$)/gi, "\n\n");
3256
+ cleaned = cleaned.replace(/(?:^|\n*)\s*(?:----------------------\s*\n*)?\[(?:ZTDS(?:™|\(TM\))?\s*Protocol|Privacy Note|Context: identifiers|Privacy Scrubber Mode|SYSTEM INSTRUCTION: DATA PRIVACY MODE)[\s\S]*?(?:in your response\.?\]|(?:\]\s*(?:\n*----------------------\s*)?))(?:\n*|$)/gi, "\n\n");
3257
+ cleaned = cleaned.replace(/(?:^|\n*)\s*(?:----------------------\s*\n*)?\[(?:ZTDS(?:™|\(TM\))?\s*Protocol|Privacy Note|Context: identifiers|Privacy Scrubber Mode|SYSTEM INSTRUCTION: DATA PRIVACY MODE|Secured by PrivacyScrubber)[^\n\]]*\]\s*(?:\n*----------------------\s*)?(?:\n*|$)/gi, "\n\n");
3258
+ cleaned = cleaned.replace(/(?:^|\n*)\s*(?:Direct identifiers in this prompt were (?:pseudonymized|client-side sanitized)|I will retain all bracketed tokens)[^\n]*(?:\n*|$)/gi, "\n\n");
2935
3259
  // 4. Strip stray leading colons, semicolons, or separators left by stripped icons/artifact headers
3260
+ cleaned = cleaned.replace(/^(?:\s*----------------------\s*\n+)+/, "");
3261
+ cleaned = cleaned.replace(/(?:\n+\s*----------------------\s*)+$/, "");
2936
3262
  cleaned = cleaned.replace(/^[:;|\-\—\–]+(?=\n|$)/, "");
2937
3263
  cleaned = cleaned.replace(/^[:;]+\s*/, "");
2938
3264
  return cleaned.trim();
@@ -2960,7 +3286,7 @@ const PROFILE_JARGON = {
2960
3286
  lookup.set(k, v);
2961
3287
  lookup.set(k.toUpperCase(), v);
2962
3288
 
2963
- const inner = k.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '');
3289
+ const inner = k.replace(/^\[|<|\{\{?|__|\]|>|\}\}?|__/g, '');
2964
3290
  const match = inner.match(/^([A-Za-z_0-9]+?)[-_]?(\d+)$/);
2965
3291
  if (match) {
2966
3292
  const label = match[1];
@@ -2972,9 +3298,15 @@ const PROFILE_JARGON = {
2972
3298
  u + ' ' + baseIndex,
2973
3299
  u + baseIndex,
2974
3300
  '[' + u + '_' + baseIndex + ']',
3301
+ '[' + u + baseIndex + ']',
2975
3302
  '<' + u + '_' + baseIndex + '>',
3303
+ '<' + u + baseIndex + '>',
2976
3304
  '{{' + u + '_' + baseIndex + '}}',
3305
+ '{{' + u + baseIndex + '}}',
3306
+ '{' + u + '_' + baseIndex + '}',
3307
+ '{' + u + baseIndex + '}',
2977
3308
  '__' + u + '_' + baseIndex + '__',
3309
+ '__' + u + baseIndex + '__',
2978
3310
  '[' + u + ' ' + baseIndex + ']',
2979
3311
  '[' + u + '-' + baseIndex + ']'
2980
3312
  ];
@@ -2990,7 +3322,7 @@ const PROFILE_JARGON = {
2990
3322
  for (let i = 0; i < keys.length; i++) {
2991
3323
  const k = keys[i];
2992
3324
  const v = sessionMap[k];
2993
- const inner = k.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '');
3325
+ const inner = k.replace(/^\[|<|\{\{?|__|\]|>|\}\}?|__/g, '');
2994
3326
  const match = inner.match(/^([A-Za-z_0-9]+?)[-_]?(\d+)$/);
2995
3327
  if (match) {
2996
3328
  const label = match[1];
@@ -3005,9 +3337,15 @@ const PROFILE_JARGON = {
3005
3337
  u + ' ' + baseIndex,
3006
3338
  u + baseIndex,
3007
3339
  '[' + u + '_' + baseIndex + ']',
3340
+ '[' + u + baseIndex + ']',
3008
3341
  '<' + u + '_' + baseIndex + '>',
3342
+ '<' + u + baseIndex + '>',
3009
3343
  '{{' + u + '_' + baseIndex + '}}',
3344
+ '{{' + u + baseIndex + '}}',
3345
+ '{' + u + '_' + baseIndex + '}',
3346
+ '{' + u + baseIndex + '}',
3010
3347
  '__' + u + '_' + baseIndex + '__',
3348
+ '__' + u + baseIndex + '__',
3011
3349
  '[' + u + ' ' + baseIndex + ']',
3012
3350
  '[' + u + '-' + baseIndex + ']'
3013
3351
  ];
@@ -3021,7 +3359,7 @@ const PROFILE_JARGON = {
3021
3359
  }
3022
3360
  }
3023
3361
 
3024
- let regexStr = '(?:\\[\\s*[A-Za-z0-9_\\-А-Яа-яЁё ]+\\s*\\]|<\\s*[A-Za-z0-9_\\-А-Яа-яЁё ]+\\s*>|\\{\\{\\s*[A-Za-z0-9_\\-А-Яа-яЁё ]+\\s*\\}\\}|__\\s*[A-Za-z0-9_\\-А-Яа-яЁё ]+\\s*__|(?<=^|[^a-zA-Z0-9_А-Яа-яЁё])[A-Za-z_А-Яа-яЁё]+[-_\\s]*\\d+)';
3362
+ let regexStr = '(?:(?:\\[|\\\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『)\\s*[*`~]*[A-Za-z0-9_\\\\\\-\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF ]+[*`~]*[.,;:!?]?\\s*(?:\\]|\\\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』)|<\\s*[A-Za-z0-9_\\\\\\-\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF ]+[.,;:!?]?\\s*>|\\{\\{?\\s*[A-Za-z0-9_\\\\\\-\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF ]+[.,;:!?]?\\s*\\}\\}?|__\\s*[A-Za-z0-9_\\\\\\-\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF ]+\\s*__|(?<=^|[^a-zA-Z0-9_\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF])[A-Za-z_\\\\\\u00C0-\\u024F\\u0400-\\u04FF\\u0590-\\u05FF]+[-_\\\\\\s]*\\d+)';
3025
3363
  if (customRegexParts.length > 0) {
3026
3364
  regexStr = '(?:' + regexStr + '|' + customRegexParts.join('|') + ')';
3027
3365
  }
@@ -3034,13 +3372,15 @@ const PROFILE_JARGON = {
3034
3372
  return entry;
3035
3373
  }
3036
3374
 
3375
+ const OUTER_TOKEN_DELIM_RE = /^(?:\[|\\u005[Bb]|%5[Bb]|&(?:amp;)?#0*91;|&(?:amp;)?#[xX]0*5[Bb];|&(?:amp;)?lbrack;|【|[|〔|「|『|<|\{\{?|__)\s*|\s*(?:\]|\\u005[Dd]|%5[Dd]|&(?:amp;)?#0*93;|&(?:amp;)?#[xX]0*5[Dd];|&(?:amp;)?rbrack;|】|]|〕|」|』|>|\}\}?|__)$/gi;
3376
+
3037
3377
  function resolveTokenValue(rawMatch, targetTokenKey, sessionMap) {
3038
3378
  if (!sessionMap) return undefined;
3039
3379
  if (sessionMap[targetTokenKey] !== undefined) return sessionMap[targetTokenKey];
3040
3380
  if (sessionMap[rawMatch] !== undefined) return sessionMap[rawMatch];
3041
- const cleanRaw = rawMatch.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '').trim();
3381
+ const cleanRaw = rawMatch.replace(OUTER_TOKEN_DELIM_RE, '').replace(/\\([_-])/g, '$1').replace(/[*`~]+/g, '').replace(/[.,;:!?]+$/, '').trim();
3042
3382
  for (const k of Object.keys(sessionMap)) {
3043
- const cleanK = k.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '').trim();
3383
+ const cleanK = k.replace(/^\[|<|\{\{?|__|\]|>|\}\}?|__/g, '').trim();
3044
3384
  if (cleanK.toLowerCase() === cleanRaw.toLowerCase()) {
3045
3385
  return sessionMap[k];
3046
3386
  }
@@ -3067,22 +3407,26 @@ const PROFILE_JARGON = {
3067
3407
  restoredCount++;
3068
3408
  return lookup.get(upper);
3069
3409
  }
3070
- // Normalized lookup: strip outer brackets/delimiters, normalize whitespace, collapse leading zeros
3071
- const inner = match.replace(/^(\[|<|\{\{|__)\s*|\s*(\]|>|\}\}|__)$/g, '').trim();
3072
- const normMatch = inner.match(/^([A-Za-z_А-Яа-яЁё0-9]+?)[-_\s]+0*(\d+)$/);
3410
+ // Normalized lookup: strip outer brackets/delimiters (including URL-encoded, JSON Unicode escapes, HTML entities, CJK fullwidth/corner) and Markdown wrappers
3411
+ const rawInner = match.replace(OUTER_TOKEN_DELIM_RE, '').replace(/\\([_-])/g, '$1').replace(/[*`~]+/g, '').trim();
3412
+ const innerPunctMatch = rawInner.match(/^([\s\S]+?)([.,;:!?])$/);
3413
+ const inner = innerPunctMatch ? innerPunctMatch[1].trim() : rawInner;
3414
+ const trailingPunct = innerPunctMatch ? innerPunctMatch[2] : '';
3415
+ const normMatch = inner.match(/^([A-Za-z_\u00C0-\u024F\u0400-\u04FF\u0590-\u05FF0-9]+?)[-_\s]*0*(\d+)$/);
3073
3416
  if (normMatch) {
3074
3417
  const normLabel = normMatch[1].replace(/[\s_-]+/g, '_').toUpperCase();
3075
3418
  const normIdx = parseInt(normMatch[2], 10);
3076
3419
  const candidates = [
3077
3420
  normLabel + '_' + normIdx,
3078
3421
  '[' + normLabel + '_' + normIdx + ']',
3422
+ '[' + normLabel + normIdx + ']',
3079
3423
  normLabel + ' ' + normIdx,
3080
3424
  normLabel + '-' + normIdx
3081
3425
  ];
3082
3426
  for (let c = 0; c < candidates.length; c++) {
3083
3427
  if (lookup.has(candidates[c])) {
3084
3428
  restoredCount++;
3085
- return lookup.get(candidates[c]);
3429
+ return lookup.get(candidates[c]) + trailingPunct;
3086
3430
  }
3087
3431
  }
3088
3432
  }
@@ -3131,14 +3475,18 @@ const PROFILE_JARGON = {
3131
3475
  } else if (lookup.has(match.toUpperCase())) {
3132
3476
  rawVal = lookup.get(match.toUpperCase());
3133
3477
  } else {
3134
- const inner = match.replace(/^(\[|<|\{\{|__)\s*|\s*(\]|>|\}\}|__)$/g, '').trim();
3135
- const normMatch = inner.match(/^([A-Za-z_А-Яа-яЁё0-9]+?)[-_\s]+0*(\d+)$/);
3478
+ const rawInner = match.replace(OUTER_TOKEN_DELIM_RE, '').replace(/\\([_-])/g, '$1').replace(/[*`~]+/g, '').trim();
3479
+ const innerPunctMatch = rawInner.match(/^([\s\S]+?)([.,;:!?])$/);
3480
+ const inner = innerPunctMatch ? innerPunctMatch[1].trim() : rawInner;
3481
+ if (innerPunctMatch) suffix = innerPunctMatch[2];
3482
+ const normMatch = inner.match(/^([A-Za-z_\u00C0-\u024F\u0400-\u04FF\u0590-\u05FF0-9]+?)[-_\s]*0*(\d+)$/);
3136
3483
  if (normMatch) {
3137
3484
  const normLabel = normMatch[1].replace(/[\s_-]+/g, '_').toUpperCase();
3138
3485
  const normIdx = parseInt(normMatch[2], 10);
3139
3486
  const candidates = [
3140
3487
  normLabel + '_' + normIdx,
3141
3488
  '[' + normLabel + '_' + normIdx + ']',
3489
+ '[' + normLabel + normIdx + ']',
3142
3490
  normLabel + ' ' + normIdx,
3143
3491
  normLabel + '-' + normIdx
3144
3492
  ];
@@ -3162,7 +3510,7 @@ const PROFILE_JARGON = {
3162
3510
  if (rawVal !== null) {
3163
3511
  restoredCount++;
3164
3512
  const escapedVal = escapeHTML(rawVal);
3165
- const cleanMatch = match.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '').trim();
3513
+ const cleanMatch = match.replace(OUTER_TOKEN_DELIM_RE, '').replace(/[*`~]+/g, '').replace(/[.,;:!?]+$/, '').trim();
3166
3514
  return `<span class="ps-restored-data entity-tag entity-revealed" title="Original Token: ${escapeHTML(cleanMatch)}">${escapedVal}</span>${escapeHTML(suffix)}`;
3167
3515
  }
3168
3516
  return match;
@@ -3177,14 +3525,17 @@ const PROFILE_JARGON = {
3177
3525
  const suffixMatch = match.match(/(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})$/);
3178
3526
  const suffix = suffixMatch ? suffixMatch[0] : '';
3179
3527
  const baseMatch = suffix ? match.slice(0, -suffix.length) : match;
3180
- const cleanMatch = baseMatch.replace(/^\[|<|\{\{|__|\]|>|\}\}|__/g, '').trim();
3528
+ const rawClean = baseMatch.replace(OUTER_TOKEN_DELIM_RE, '').replace(/[*`~]+/g, '').trim();
3529
+ const punctMatch = rawClean.match(/^([\s\S]+?)([.,;:!?])$/);
3530
+ const cleanMatch = punctMatch ? punctMatch[1].trim() : rawClean;
3531
+ const innerPunct = punctMatch ? punctMatch[2] : '';
3181
3532
  const token = `[${cleanMatch}]`;
3182
3533
 
3183
3534
  const val = resolveTokenValue(baseMatch, token, sessionMap) ?? resolveTokenValue(baseMatch, cleanMatch, sessionMap);
3184
3535
  if (val !== undefined) {
3185
3536
  restoredCount++;
3186
3537
  const escapedVal = escapeHTML(val);
3187
- return `<span class="ps-restored-data entity-tag entity-revealed" title="Original Token: ${escapeHTML(token)}">${escapedVal}</span>${escapeHTML(suffix)}`;
3538
+ return `<span class="ps-restored-data entity-tag entity-revealed" title="Original Token: ${escapeHTML(token)}">${escapedVal}</span>${escapeHTML(innerPunct + suffix)}`;
3188
3539
  }
3189
3540
  return match;
3190
3541
  });
@@ -3201,12 +3552,15 @@ const PROFILE_JARGON = {
3201
3552
  const suffixMatch = match.match(/(?:'s|’s|s|[а-яёА-ЯЁ]{1,3})$/);
3202
3553
  const suffix = suffixMatch ? suffixMatch[0] : '';
3203
3554
  const baseMatch = suffix ? match.slice(0, -suffix.length) : match;
3555
+ const rawClean = baseMatch.replace(OUTER_TOKEN_DELIM_RE, '').replace(/[*`~]+/g, '').trim();
3556
+ const punctMatch = rawClean.match(/^([\s\S]+?)([.,;:!?])$/);
3557
+ const innerPunct = punctMatch ? punctMatch[2] : '';
3204
3558
 
3205
3559
  const val = sessionMap[rule.token] ?? resolveTokenValue(baseMatch, rule.token, sessionMap);
3206
3560
  if (val !== undefined) {
3207
3561
  restoredCount++;
3208
3562
  const escapedVal = escapeHTML(val);
3209
- return `<span class="ps-restored-data entity-tag entity-revealed" title="Original Token: ${escapeHTML(rule.token)} (Fuzzy Match)">${escapedVal}</span>${escapeHTML(suffix)}`;
3563
+ return `<span class="ps-restored-data entity-tag entity-revealed" title="Original Token: ${escapeHTML(rule.token)} (Fuzzy Match)">${escapedVal}</span>${escapeHTML(innerPunct + suffix)}`;
3210
3564
  }
3211
3565
  return match;
3212
3566
  });