@privacyscrubber/mcp-server 2.1.0 → 2.2.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/README.md +125 -22
- package/index.js +486 -21
- package/manifest.json +4 -2
- package/package.json +7 -2
- package/pii-masking-run.js +213 -54
- package/ps-license-manager.cjs +257 -0
- package/ps-pii-engine.cjs +79 -36
- package/ps-pii-engine.js +79 -36
- package/scrubber-core.cjs +6 -6
- package/server.json +3 -3
package/ps-pii-engine.js
CHANGED
|
@@ -47,9 +47,9 @@ let REGEX_RULES = [
|
|
|
47
47
|
|
|
48
48
|
// Professional IDs & Organizations
|
|
49
49
|
{ type: 'NAME', isContextName: true, isCorporateName: true, regex: /\b(?:[A-Z][A-Za-z0-9&.,'-]*[ \t\xA0]+){1,5}(?:Inc\.?|LLC|Corp\.?|Corporation|Ltd\.?|Limited|Co\.?|Company|Group|Holdings|Solutions|Services|Technologies|Logistics|Industries|Capital|Bank|Partners|LLP|PLLC)(?:\s+(?:LLC|Inc\.?|Corp\.?|Ltd\.?|USA|Group))?\b/g },
|
|
50
|
-
{ 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 },
|
|
50
|
+
{ 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 },
|
|
51
51
|
{ 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 },
|
|
52
|
-
{ type: 'ID', regex: /(?:\b(?:Box\s+d\b|d\.\s*(?:Control
|
|
52
|
+
{ 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 },
|
|
53
53
|
{ type: 'ID', regex: /\bEEID[ -]?\d{4,}\b/gi },
|
|
54
54
|
{ type: 'ID', regex: /\bEMP[-_]\d{3,}\b/gi },
|
|
55
55
|
|
|
@@ -92,7 +92,7 @@ let REGEX_RULES = [
|
|
|
92
92
|
{ type: 'ID', regex: /\b\d{2}\/\d{2}\/\d{4}\b/g },
|
|
93
93
|
|
|
94
94
|
// Phone Numbers
|
|
95
|
-
{ type: 'PHONE', regex: /(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/
|
|
95
|
+
{ type: 'PHONE', regex: /(?<!\w)(?:\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?:\s*(?:ext\.?|x)\s*\d{1,5})?\b/gi },
|
|
96
96
|
{ type: 'PHONE', regex: /\+\d{1,3}[ \t.-]+(?:\(?\d{1,4}\)?[ \t.-]+)?\d{2,8}(?:[ \t.-]+\d{2,8}){0,4}\b/g },
|
|
97
97
|
{ type: 'PHONE', regex: /\+?[1-9]\d{1,3}[\s.-]\(?\d{1,4}\)?[\s.-]\d{2,4}[\s.-]\d{4}/g },
|
|
98
98
|
{ type: 'PHONE', regex: /(?:\+44\s?7\d{3}|\(?07\d{3}\)?)\s?\d{3}\s?\d{3}\b/g },
|
|
@@ -109,16 +109,18 @@ let REGEX_RULES = [
|
|
|
109
109
|
// Generalized Name Detection (First [Middle] Last) — max 1 middle word to avoid grabbing job titles
|
|
110
110
|
// Middle word must be: a particle (van/de/etc.), a single initial (A. or A), or a capitalized word of ≥2 lowercase letters
|
|
111
111
|
{ type: 'NAME', isAggressiveName: true, regex: /(?<=^|[^\p{L}\p{N}_])(?:\p{Lu}[\p{Ll}'-]*\p{Ll}|\p{Lu}[\p{Ll}'-]*[\p{Lu}'-][\p{Ll}'-]*)(?:[ \t\xA0]+(?:\p{Lu}[\p{Ll}'-]*\p{Ll}|\p{Lu}[\p{Ll}'-]*[\p{Lu}'-][\p{Ll}'-]*|\p{Lu}\.?|van|von|de|di|da|la|le|del|du|der|van[ \t\xA0]+de|van[ \t\xA0]+der)){0,1}[ \t\xA0]+(?:\p{Lu}[\p{Ll}'-]*\p{Ll}|\p{Lu}[\p{Ll}'-]*[\p{Lu}'-][\p{Ll}'-]*)(?:'s)?(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
|
|
112
|
-
// Payroll Format Names (e.g. "BARKER, KELLY", "
|
|
113
|
-
{ type: 'NAME', regex: /\b[A-Z]
|
|
112
|
+
// Payroll & Inverted Format Names (e.g. "BARKER, KELLY", "Vance-Soto, Eleanor", "Kaufman-Alvarez, Liam T.")
|
|
113
|
+
{ type: 'NAME', regex: /\b[A-Z][a-zA-Z'’-]+(?:-[A-Za-z'’-]+)?,\s+[A-Z][a-zA-Z'’-]+(?:\s+[A-Z]\.?(?!\w)|\s+[A-Z][a-zA-Z'’-]+)*\b/g },
|
|
114
114
|
// All-Caps Names (2–3 words, supporting single-letter middle initial e.g. "KELLY M BARKER", "JOHN M BARKER")
|
|
115
115
|
{ 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 },
|
|
116
116
|
// ALL-CAPS first + middle initial(s) with optional spaces + ALL-CAPS last name
|
|
117
117
|
{ 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 },
|
|
118
118
|
// ALL-CAPS first name + optional middle initial(s) with optional spaces + Mixed-Case last name
|
|
119
119
|
{ 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 },
|
|
120
|
-
// Names with Honorifics (with or without period, supporting
|
|
121
|
-
{ type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])(?:Mr|Mrs|Ms|Dr|Prof|Hon|
|
|
120
|
+
// Names with Honorifics (with or without period, supporting middle initials and hyphenated surnames) (Unicode-safe)
|
|
121
|
+
{ type: 'NAME', regex: /(?<=^|[^\p{L}\p{N}_])(?:Mr|Mrs|Ms|Dr|Prof|Hon|Rev|Judge|Atty|Doctor|Professor)(?:\.|\b)[ \t\xA0]+(?:\p{Lu}[\p{L}'-]+)(?:[ \t\xA0]+(?:\p{Lu}\.?|\p{Lu}[\p{L}'-]+)){0,2}(?:[ \t\xA0]+(?:\p{Lu}[\p{L}'-]+))(?=[^\p{L}\p{N}_]|$)(?![ \t\xA0]*:)/gu },
|
|
122
|
+
// Names with Quoted or Parenthetical Nicknames (e.g. Judith "Judy" Diaz, Robert (Bob) Smith)
|
|
123
|
+
{ 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 },
|
|
122
124
|
|
|
123
125
|
// W-2 Box c Employer Block (Name, Address, and Zip Code)
|
|
124
126
|
{ 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 },
|
|
@@ -129,7 +131,11 @@ let REGEX_RULES = [
|
|
|
129
131
|
// Contextual Last Names (Last name, Surname, Family name)
|
|
130
132
|
{ type: 'NAME', isContextName: true, regex: /(?:(?: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 },
|
|
131
133
|
// Contextual General Names (Employee, Principal Investigator, Physician, Doctor, etc.)
|
|
132
|
-
{ 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)\s+(?:Name|Full\s+Name)|(?:Principal\s+Investigator|Investigator|Physician|Doctor|Supervisor|Reporting\s+To|Prepared\s+By|Attention|Attn|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)\s*[:=#])[:\s
|
|
134
|
+
{ 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)\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)\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 },
|
|
135
|
+
// Contextual Nicknames & Preferred Names (e.g. "Nickname: Judy", "Preferred Name: Alex", "AKA: Bob", "Goes by: Sam")
|
|
136
|
+
{ type: 'NAME', isContextName: true, regex: /(?:(?:Patient(?:'s)?|Client(?:'s)?|Child(?:'s)?|Employee(?:'s)?|Person(?:'s)?)\s+)?(?:Nickname|Nick\s+Name|Preferred\s+(?:First\s+)?Name|Also\s+Known\s+As|AKA|Alias|Goes\s+by)[\s:#]+(?:\b|\b\s*)([A-Z\p{Lu}][A-Za-z0-9&.,\s'-]{1,30}?)(?=\r?\n|$|\s{3,}|\t|DOB|Date|Phone|Address|Emergency|Relationship|Notes|MRN|\()/giu },
|
|
137
|
+
// Emergency Contacts (Standard, Inverted Last First, Single Name, Table Cells, Extended Relationships, Brackets & Phone Lookahead)
|
|
138
|
+
{ type: 'NAME', isContextName: true, regex: /(?:(?:^|[^\p{L}\p{N}_]|\|)\s*(?:In\s+Case\s+of\s+Emergency(?:\s*\(ICE\))?|ICE(?:[ \t\xA0]*(?:Contact|Notification|Person|Name))?|Emergency(?:[ \t\xA0]*(?:Contact|Notification|Person|Info|Details))?|Primary\s+Emergency\s+Contact|Secondary\s+Emergency\s+Contact|Alternate\s+Emergency\s+Contact|Person\s+to\s+notify\s+(?:in\s+case\s+of\s+emergency|in\s+emergency))(?:[ \t\xA0]*(?:[12#]|#\s*\d|\(\d\)))?(?:[ \t\xA0]*(?:Person|Name|Full\s+Name))?(?:[ \t\xA0]*(?:[\/&+-]|and)[ \t\xA0]*(?:Relationship|Relation|Proxy|Guarantor|Guardian))?(?:[ \t\xA0]*\((?:Primary|Secondary|Name|Relationship|Relation|Proxy|Guarantor|Legal\s+Guardian|Full\s+Name)\))?)[:=#|\/\t\s-]*[:=#|\/\t-][ \t\xA0]*([A-Z\p{Lu}][A-Za-z0-9&.'’]*(?:[ \t\xA0]+[A-Za-z0-9&.'’]+)*(?:,[ \t\xA0]*(?!(?:Relationship|Relation|Rel|Mother|Father|Spouse|Wife|Husband|Partner|Son|Daughter|Child|Parent|Brother|Sister|Sibling|Friend|Aunt|Uncle|Cousin|Guardian|Caregiver|Carer|Neighbor|Relative|Family|Grandmother|Grandfather|Grandparent|Grandson|Granddaughter|Stepmother|Stepfather|Stepson|Stepdaughter|Step-mother|Step-father|In-law|Mother-in-law|Father-in-law|Sister-in-law|Brother-in-law|Son-in-law|Daughter-in-law|Fiancé|Fiance|Fiancee|Significant\s+Other|Domestic\s+Partner|Life\s+Partner|Roommate|Colleague|Coworker|Co-worker|Legal\s+Guardian|Case\s+Worker|Caseworker|Social\s+Worker|Foster\s+Parent|POA|Power\s+of\s+Attorney|Healthcare\s+Proxy|Medical\s+Proxy|Proxy|Next\s+of\s+Kin|Aide)\b)[A-Z\p{Lu}][A-Za-z0-9&.'’]*(?:[ \t\xA0]+[A-Za-z0-9&.'’]+)*)?)(?=[ \t\xA0]*(?:\([A-Za-z0-9\s\/&':.+–-]+\)|\[[A-Za-z0-9\s\/&':.+–-]+\]|\{[A-Za-z0-9\s\/&':.+–-]+\}|[,\/|\-–—]{1,3}\s*(?:Relationship|Relation|Rel|Mother|Father|Spouse|Wife|Husband|Partner|Son|Daughter|Child|Parent|Brother|Sister|Sibling|Friend|Aunt|Uncle|Cousin|Guardian|Caregiver|Carer|Neighbor|Relative|Family|Grandmother|Grandfather|Grandparent|Grandson|Granddaughter|Stepmother|Stepfather|Stepson|Stepdaughter|Step-mother|Step-father|In-law|Mother-in-law|Father-in-law|Sister-in-law|Brother-in-law|Son-in-law|Daughter-in-law|Fiancé|Fiance|Fiancee|Significant\s+Other|Domestic\s+Partner|Life\s+Partner|Roommate|Colleague|Coworker|Co-worker|Legal\s+Guardian|Case\s+Worker|Caseworker|Social\s+Worker|Foster\s+Parent|POA|Power\s+of\s+Attorney|Healthcare\s+Proxy|Medical\s+Proxy|Proxy|Next\s+of\s+Kin|Aide)\b|[,\/|\-–—]?[ \t\xA0]*(?:Phone|Tel|Cell|Mobile|Work|Home|Office|Direct|Telephone)[:\s#-]*|[,\/|\-–—]?[ \t\xA0]*(?:Relationship|Relation|Rel)[:\s#-]*|[,\/|\-–—]?[ \t\xA0]*(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b|[,\/|\-–—]?[ \t\xA0]*\d{3}[-.\s]?\d{4}\b|[ \t\xA0]*(?:\||\r?\n|$|\s{2,}|\t)))/gui },
|
|
133
139
|
// Box e shorthand
|
|
134
140
|
{ type: 'NAME', isContextName: true, regex: /\b(?:Box\s+e)\s*[:#-]\s*([A-Za-z0-9&.,\s'-]{2,40})/gi }
|
|
135
141
|
];
|
|
@@ -137,17 +143,29 @@ let REGEX_RULES = [
|
|
|
137
143
|
let PROFILE_RULES = {
|
|
138
144
|
general: [],
|
|
139
145
|
legal: [
|
|
146
|
+
// Legal Party & Role-Prefixed Names (Claimant, Respondent, Plaintiff, Defendant, Petitioner, Appellant, Principal, Grantor, Attorney-in-Fact, Counsel)
|
|
147
|
+
{ type: 'NAME', isContextName: true, regex: /\b(?:Registered\s+Representative|Claimant|Respondent|Plaintiff|Defendant|Petitioner|Appellant|Appellee|Principal|Grantor|Grantee|Trustee|Trustor|Fiduciary|Attorney-in-Fact|Designated\s+Attorney-in-Fact|Client\s*(?:\/\s*Principal)?|Lead\s+Counsel|Counsel|Managing\s+Partner|Escrow\s+(?:Trust\s+)?Officer)\s*[:#,]?\s*(?:(?:Dr|Mr|Mrs|Ms|Prof)\.?\s+)?([A-Z][a-zA-Z'’-]+(?:-[A-Z][a-zA-Z'’-]+)?(?:,\s+[A-Z][a-zA-Z'’-]+(?:\s+[A-Z]\.?)?|\s+(?:[A-Z]\.?\s+)?[A-Z][a-zA-Z'’-]+(?:-[A-Z][a-zA-Z'’-]+)?))/gi },
|
|
140
148
|
{ type: 'LEGAL', regex: /\bCASE[-_][A-Z0-9_-]{4,}\b/gi },
|
|
141
149
|
{ type: 'LEGAL', regex: /\bMATTER[-_][A-Z0-9_-]{4,}\b/gi },
|
|
142
150
|
{ type: 'LEGAL', regex: /\b[A-Z]{2,4}[- ]?\d{2}[- ]?\d{4,}\b/g },
|
|
143
|
-
{ type: 'PRIVILEGE', regex:
|
|
151
|
+
{ type: 'PRIVILEGE', regex: /\bATTORNEY[- ]CLIENT[- ]PRIVILEGE[DS]?\b/gi },
|
|
144
152
|
{ type: 'LEGAL', regex: /\b(?:BATES|PROD|PLTF|DEFT|CONF|EXHIBIT)[-_ ]?[A-Z0-9]{2,10}[-_ ]?\d{4,10}\b/gi },
|
|
145
|
-
|
|
146
|
-
{ type: '
|
|
153
|
+
// Case / Proceeding / Arbitration Numbers (including OCR homoglyphs O/0, I/1, l/1)
|
|
154
|
+
{ type: 'LEGAL', regex: /\b(?:Case|Matter|Arbitration|Docket|Proceeding)\s*(?:(?:No\.?|Number|#)[:\s#]*|[:#]\s*)([A-Za-z0-9_-]{4,25})\b/gi },
|
|
155
|
+
{ type: 'LEGAL', regex: /\b(?:\d{1,2}:)?\d{2,4}[-_ ]?(?:CV|CR|MDL|BK|MC|MISC|MJ|PO|CA|AP|PR|AR)[-_ ][A-Za-z0-9_-]{3,15}\b/gi },
|
|
156
|
+
// Bar registration & CRD / Broker / Representative IDs (including OCR homoglyphs)
|
|
157
|
+
{ type: 'ID', regex: /\b(?:STATE\s+BAR|BAR\s+(?:NO\.?|#|ID)|SBN|ATTORNEY\s+REG(?:ISTRATION)?)[:\s#-]*([0-9OIloA-Za-z]{4,10})\b/gi },
|
|
158
|
+
{ type: 'ID', regex: /\b(?:CRD|CIK|Broker\s*ID|Rep(?:resentative)?\s*ID|Advisor\s*ID|Registration\s*(?:No\.?|#)?)[:\s#]*([0-9OIloA-Za-z]{5,12})\b/gi },
|
|
147
159
|
{ type: 'LEGAL', regex: /\b(?:RETAINER|ENGAGEMENT|REPRESENTATION)[-_: ]*(?:AGREEMENT|ID|REF)?[:\s#-]*[A-Z0-9_-]{4,20}\b/gi },
|
|
148
160
|
{ type: 'LEGAL', regex: /\bECLI:[A-Z]{2}:[A-Za-z0-9]{1,7}:\d{4}:[A-Za-z0-9.]{1,25}\b/g },
|
|
149
161
|
{ type: 'ID', regex: /\bPCT\/[A-Z]{2}\d{4}\/\d{6}\b/g },
|
|
150
|
-
{ type: 'ID', regex: /\b(?:TX|TXu|VA|VAu|PA|PAu|SR|SRu|SE|SEu|RE)\s*-?\d[-\d ]{5,9}\d\b/g }
|
|
162
|
+
{ type: 'ID', regex: /\b(?:TX|TXu|VA|VAu|PA|PAu|SR|SRu|SE|SEu|RE)\s*-?\d[-\d ]{5,9}\d\b/g },
|
|
163
|
+
// Escrow / Trust / Settlement / Disputed Account Numbers & ABA Routing
|
|
164
|
+
{ type: 'FINANCIAL', regex: /\b(?:Escrow\s+(?:Account|Acct|Deposit)|Trust\s+Account|Settlement\s+Account|Disputed\s+Account)\s*(?:Number|Num|No\.?|#)?[:\s#]+([A-Za-z0-9-]{5,25})\b/gi },
|
|
165
|
+
{ type: 'FINANCIAL', regex: /\b(?:ABA\s+Routing|Routing\s+Transit|Fedwire\s+Routing)\s*(?:Number|Num|No\.?|#)?[:\s#]+(\d{9})\b/gi },
|
|
166
|
+
// Legal Financial Amounts (lookbehind + standalone)
|
|
167
|
+
{ type: 'FINANCIAL', regex: /(?<=\b(?:Settlement(?:\s+Amount)?|Retainer(?:\s+Fee)?|Hourly\s+Rate|Damages(?:\s+Claimed)?|Legal\s+Fees?|Sanctions?|Judgement|Judgment|Disgorgement|Penalty|(?:Prejudgment\s+)?Interest|Escrow(?:\s+Funds?|\s+Amount)?|Fine|Restitution)(?:[\s:]+(?:of)?[\s:]*|[:\s]+))(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])|[0-9,.'’]{3,}\b)/gi },
|
|
168
|
+
{ type: 'FINANCIAL', regex: /(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹]))/gi }
|
|
151
169
|
],
|
|
152
170
|
hr: [
|
|
153
171
|
{ type: 'NAME', isContextName: true, regex: /(?:Candidate|Applicant|Employee|Reporting To|Manager|Mentored by|Direct Report)[\s:]+([A-Z][a-z]*(?:\s+[A-Z][a-z]*)?)/g },
|
|
@@ -181,17 +199,26 @@ let PROFILE_RULES = {
|
|
|
181
199
|
{ type: 'ID', regex: /\b(?:FEIN|EIN|TIN|Tax\s*ID)[:\s#]*(\d{2}-\d{7})\b/gi },
|
|
182
200
|
{ type: 'ID', regex: /\b(?:CRD|CIK|Advisor\s*ID|Broker\s*ID)[:\s#]*(\d{5,10})\b/gi },
|
|
183
201
|
{ type: 'ID', regex: /\b(?:LEI|LEGAL[\s-]ENTITY[\s-]IDENTIFIER)[:\s#-]*[0-9A-Z]{20}\b/gi },
|
|
184
|
-
{ type: 'ID', regex: /\bBBG[0-9A-Z]{9}\b/g }
|
|
202
|
+
{ type: 'ID', regex: /\bBBG[0-9A-Z]{9}\b/g },
|
|
203
|
+
{ type: 'FINANCIAL', regex: /(?<=\b(?:Wire(?:\s+(?:Transfer|Amount))?|Transfer|Balance|Deposit|Withdrawal|Settlement|Principal|Interest|Remittance|Payout|Transaction(?:\s+Amount)?|Payment(?:\s+Amount)?|Total|Amount)[:\s]+)(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])|[0-9,.'’]{3,}\b)/gi }
|
|
185
204
|
],
|
|
186
205
|
medical: [
|
|
187
|
-
{ type: '
|
|
206
|
+
{ type: 'NAME', isContextName: true, regex: /\b(?:PATIENT|Pt\.?|LEGAL\s+GUARDIAN(?:\/PROXY)?|PROXY|EMERGENCY\s+CONTACT|ATTENDING\s+PHYSICIAN|ATTENDING|SURGEON|PHYSICIAN|PROVIDER|SUBSCRIBER|GUARDIAN)\s*[:#|]?\s*(?!\b(?:NPI|MRN|DOB|ID|DATE|RECORD|PHONE)\b)(?:(?:Dr|Mr|Mrs|Ms|Prof)\.?\s+)?([A-Z][a-zA-Z'’-]+(?:-[A-Z][a-zA-Z'’-]+)?(?:,\s+[A-Z][a-zA-Z'’-]+(?:\s+[A-Z]\.?)?|\s+(?:[A-Z]\.?\s+)?[A-Z][a-zA-Z'’-]+(?:-[A-Z][a-zA-Z'’-]+)?))/gi },
|
|
207
|
+
// HL7 v2 PID-5 Caret-Delimited Patient / Provider Names (e.g. "Guillermo^Maricela^C", "Vance-Soto^Eleanor^M")
|
|
208
|
+
{ type: 'NAME', isContextName: true, isHL7Name: true, regex: /\b[A-Za-z'’-]{2,}\^[A-Za-z'’-]{2,}(?:\^[A-Za-z'’.-]+){0,4}\b/g },
|
|
209
|
+
// HL7 v2 Segment Delimited Dates (YYYYMMDD in PID-7, MSH-7, etc. e.g. "||19820412|F")
|
|
210
|
+
{ type: 'DATE', regex: /(?<=[|:])(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])(?=[|:\s])/g },
|
|
211
|
+
{ type: 'PHI', regex: /\b(?:MRN|Patient\s+(?:ID|Number|No\.?|#)|Medical\s+Record\s+(?:No\.?|Num(?:ber)?|#)|EHR(?:\s*(?:ID|Record|Number|No\.?|#))?)[\s:#.]+([A-Za-z0-9\/-]+)/gi },
|
|
188
212
|
{ type: 'DATE', regex: /\b(?:DOB|Date of Birth|BIRTHDAY)[\s:]+([0-9./-]{6,10})\b/gi },
|
|
189
|
-
{ type: 'PHI', regex: /\
|
|
213
|
+
{ type: 'PHI', regex: /\b(?:MRN|EHR)[-_ .]*[A-Za-z0-9\/-]{4,}\b/gi },
|
|
190
214
|
{ type: 'ID', regex: /\b(?:Insurance\s+(?:ID|No\.?|Number|#)|Policy(?:\s*(?:ID|No\.?|Number|#)|[:#])|Member\s*(?:ID|No\.?|Number|#|[:#])|Subscriber\s*(?:ID|No\.?|Number|#|[:#])|Group\s*(?:ID|No\.?|Number|#|[:#])|Plan\s*(?:ID|No\.?|Number|#|[:#])|Health(?:\s+Plan)?\s*(?:ID|No\.?|Number|#)|Rx\s*(?:ID|No\.?|Number|Group|BIN|PCN|#))[:\s#]+([A-Za-z0-9-]{3,30})\b/gi },
|
|
191
215
|
{ type: 'ID', regex: /\b(?:Health\s+Plan(?:\s+Beneficiary)?|Beneficiary(?:\s+No\.?|\s+Number)?|HPN)[:\s#]+([A-Za-z0-9-]+)/gi },
|
|
192
216
|
{ type: 'ID', regex: /\bHPN[-_][A-Za-z0-9-]+\b/gi },
|
|
193
217
|
{ type: 'ID', regex: /\b(?:BCB|BCBS|AETNA|CIGNA|UHC|HUMANA|MEDICARE|MEDICAID)[-_A-Za-z0-9]+\b/gi },
|
|
194
|
-
{ type: 'ID', regex: /\b(?:NPI|National
|
|
218
|
+
{ type: 'ID', regex: /\b(?:NPI|National\s+Provider\s+(?:Identifier|ID)|Provider\s+(?:NPI|ID)|Attending\s+(?:NPI|Provider\s+ID)|Physician\s+NPI)[:\s#.]*([0-9OIlo]{10})\b/gi },
|
|
219
|
+
{ type: 'ID', regex: /\(01\)\d{14}(?:\(\d{2}\)[A-Za-z0-9-]+)+/g },
|
|
220
|
+
{ type: 'LOCATION', regex: /\b(?:Pavilion|Ward|Bed|Room|Rm\.?|Suite|Bldg|Building|Floor|Wing|Tower|Unit)\s+[A-Za-z0-9-]+\b/gi },
|
|
221
|
+
{ type: 'DATE', regex: /\b(?: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}\b/gi },
|
|
195
222
|
{ type: 'ID', regex: /\b(?:Device\s+(?:Identifier|ID|Serial|No\.?|Number)|UDI)[:\s#]+([A-Za-z0-9-]+)/gi },
|
|
196
223
|
{ type: 'ID', regex: /\bUDI[-_][A-Za-z0-9-]+\b/gi },
|
|
197
224
|
{ type: 'ID', regex: /\b(?:Vehicle\s+(?:Serial|ID|Identification(?:\s+Number)?|No\.?|Number)|VIN)[:\s#]+([A-Za-z0-9-]+)/gi },
|
|
@@ -285,7 +312,7 @@ let PROFILE_RULES = {
|
|
|
285
312
|
{ type: 'ID', regex: /\bPARCEL[- ]?\d{5,15}\b/gi },
|
|
286
313
|
{ type: 'ID', regex: /\b(?:APN|Assessor(?:'s)?\s*Parcel(?:\s*Number)?|Parcel\s*(?:ID|Number|No\.?|#))[:\s#-]*(\d{2,5}[-\s]\d{2,5}[-\s]\d{2,5}(?:[-\s]\d{1,4})?|\d{6,15})\b/gi },
|
|
287
314
|
{ type: 'ID', regex: /\bTENANT[-_]ID[-_][0-9]{4,}\b/gi },
|
|
288
|
-
{ type: 'FINANCIAL', regex: /(?<=\b(?:RENT|LEASE|ESCROW)[\s:]+)(?:[$€£¥₪₽₹]
|
|
315
|
+
{ type: 'FINANCIAL', regex: /(?<=\b(?:RENT|LEASE|ESCROW|Purchase\s+Price|Listing\s+Price|Asking\s+Price|Down\s+Payment|Mortgage(?:\s+Balance)?|Closing\s+Costs?|Appraisal(?:\s+Value)?)[\s:]+)(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])|[0-9,.'’]{3,}\b)/gi },
|
|
289
316
|
{ type: 'SECRET', regex: /\b(?:GATE|DOOR|LOBBY)[-_ ](?:CODE|PIN)[\s:]*\d{4,6}\b/gi },
|
|
290
317
|
{ type: 'ID', regex: /\b(?:Escrow\s*(?:Number|No\.?|#|Account|File|Order)|Escrow)[:\s#]*([A-Za-z0-9-]{5,18})\b/gi },
|
|
291
318
|
{ type: 'ID', regex: /\b(?:Title\s*Policy|Title\s*Order|Title\s*Commitment)\s*(?:#|ID|No\.?)?[:\s#]*([A-Z0-9-]{6,20})\b/gi },
|
|
@@ -396,7 +423,7 @@ let PROFILE_RULES = {
|
|
|
396
423
|
{ type: 'FINANCIAL', regex: /\b(?:Policy|Contract)\s*(?:No\.?|Number|#)[:\s]+[A-Z0-9][A-Z0-9\-]{3,14}\b/gi },
|
|
397
424
|
{ type: 'ID', regex: /\bCRD\s*#?\s*\d{4,8}\b/gi },
|
|
398
425
|
{ type: 'FINANCIAL', regex: /\b(?:Annual\s+(?:Distribution|Withdrawal)|RMD|Required\s+Minimum\s+Distribution)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’]+\b/gi },
|
|
399
|
-
{ type: 'FINANCIAL', regex:
|
|
426
|
+
{ type: 'FINANCIAL', regex: /(?<=\b(?:Portfolio(?:\s+Value)?|Market\s+Value|Net\s+Worth|AUM|Total\s+Assets)[:\s]+)(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])|[0-9,.'’]{3,}\b)/gi },
|
|
400
427
|
{ type: 'FINANCIAL', regex: /\b(?:Roth\s+)?(?:IRA|401k|401\(k\)|403b|403\(b\)|SEP|SIMPLE)\s*(?:Account|Acct|Plan)?\s*(?:No|Number|#)?[:\s#]*[A-Z0-9]{4,15}\b/gi },
|
|
401
428
|
{ type: 'ID', regex: /\bCUSIP[:\s#]*([0-9A-Z]{9})\b/gi },
|
|
402
429
|
{ type: 'ID', regex: /\bISIN[:\s#]*([A-Z]{2}[0-9A-Z]{9}[0-9])\b/gi },
|
|
@@ -409,7 +436,7 @@ let PROFILE_RULES = {
|
|
|
409
436
|
{ type: 'FINANCIAL', regex: /\b(?:Loss|Claim\s+Amount|Claim\s+Settlement|Settlement|Reserve|Indemnity)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’]+\b/gi },
|
|
410
437
|
{ type: 'ID', regex: /\b(?:(?:Assigned\s+)?Adjuster\s+(?:ID|No)|Claim\s+Rep(?:\.|resentative)?)[:\s#]*[-A-Z0-9]{4,15}\b/gi },
|
|
411
438
|
{ type: 'ID', regex: /\bNAIC(?:\s+(?:Company\s+)?(?:Code|CoCode|No\.?|Number|#))?[:\s#]*(\d{5})\b/gi },
|
|
412
|
-
{ type: 'FINANCIAL', regex: /\b(?:Deductible|Premium|Coverage\s+Amount)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'
|
|
439
|
+
{ type: 'FINANCIAL', regex: /\b(?:Deductible|Premium|Coverage\s+Amount)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’\u2018\u2019]+\b/gi },
|
|
413
440
|
{ type: 'ID', regex: /\b[A-HJ-NPR-Z0-9]{17}\b/g },
|
|
414
441
|
{ type: 'ID', regex: /\b(?:Insured|Named\s+Insured)[:\s]+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+/g },
|
|
415
442
|
{ type: 'ID', regex: /\b(?:Agent|Producer)\s*(?:Code|No|ID)[:\s#]*[-A-Z0-9]{4,15}\b/gi },
|
|
@@ -421,15 +448,17 @@ let PROFILE_RULES = {
|
|
|
421
448
|
accounting: [
|
|
422
449
|
{ type: 'ID', regex: /\b(?:EIN|FEIN|Tax\s+ID)[:\s#]*\d{2}-\d{7}\b/gi },
|
|
423
450
|
{ type: 'FINANCIAL', regex: /\b(?:AGI|Adjusted\s+Gross\s+Income|Taxable\s+Income|Total\s+Income)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’]+\b/gi },
|
|
424
|
-
{ type: 'FINANCIAL', regex: /\b(?:Form\s+|Withheld\s+)?Box\s+\d{1,2}[a-z]?(?:\s*:\s*[A-Z])?\s*[:\s]+(?:[
|
|
451
|
+
{ type: 'FINANCIAL', regex: /\b(?:Form\s+|Withheld\s+)?Box\s+\d{1,2}[a-z]?(?:\s*:\s*[A-Z])?\s*[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’\u2018\u2019]+\b/gi },
|
|
425
452
|
{ type: 'ID', regex: /\b(?:Form|Schedule)\s+(?:1040|1040-SR|W-2|W-4|1099-[A-Z]{1,4}|K-1|941|990|4562)\b/gi },
|
|
426
|
-
{ type: 'FINANCIAL', regex: /\b(?:Federal|State|Assessed)?\s*(?:Tax\s+Due|Balance\s+Due|Overpayment|Refund)[:\s]+(?:[
|
|
453
|
+
{ type: 'FINANCIAL', regex: /\b(?:Federal|State|Assessed)?\s*(?:Tax\s+Due|Balance\s+Due|Overpayment|Refund)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’\u2018\u2019]+\b/gi },
|
|
427
454
|
{ type: 'ID', regex: /\b(?:State\s+Tax\s+ID|SUI|UI\s+Account\s+No)[:\s#]*[A-Z]{0,3}[-]?\d{4,15}\b/gi },
|
|
428
|
-
{ type: 'FINANCIAL', regex: /\b(?:Gross|Net|Medicare|Social\s+Security)\s+(?:Pay|Wages|Tips|Withholding)[:\s]+(?:[
|
|
455
|
+
{ type: 'FINANCIAL', regex: /\b(?:Gross|Net|Medicare|Social\s+Security)\s+(?:Pay|Wages|Tips|Withholding)[:\s]+(?:[$€£¥₪₽₹]|(?:USD|EUR|GBP|CHF|ILS|RUB)\s?)[\d,.'’\u2018\u2019]+\b/gi },
|
|
429
456
|
{ type: 'ID', regex: /\b(?:CAF(?:\s+ID)?|(?:Practitioner|Tax\s+Preparer)\s+(?:PIN|ID)|PTIN)[:\s#]*[A-Z0-9-]{6,12}\b/gi },
|
|
430
457
|
{ type: 'ID', regex: /\bEFIN[:\s#]*\d{6}\b/gi },
|
|
431
458
|
{ type: 'ID', regex: /\bGL[-_\s]*(?:Account|Acct|Code)?[:\s#]*\d{4,10}\b/gi },
|
|
432
|
-
{ type: 'ID', regex: /\b(?:Cost\s+Center|CC)[-_\s]*(?:ID|Code|#)?[:\s#]*[A-Z0-9_-]*\d[A-Z0-9_-]*\b/gi }
|
|
459
|
+
{ type: 'ID', regex: /\b(?:Cost\s+Center|CC)[-_\s]*(?:ID|Code|#)?[:\s#]*[A-Z0-9_-]*\d[A-Z0-9_-]*\b/gi },
|
|
460
|
+
{ 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 },
|
|
461
|
+
{ type: 'FINANCIAL', regex: /(?<=\b(?:Invoice(?:\s+Total)?|Amount\s+Due|Subtotal|Total\s+Due|Payment(?:\s+Amount)?|Ledger\s+Balance|Credit|Debit|Net\s+Amount|Total\s+Paid|Billed|Remittance)[:\s]+)(?:(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\s*|[$€£¥₪₽₹]\s*)[0-9,.'’]*\d[KMB]?\b|\b\d[0-9,.'’]*\d?[KMB]?\s*(?:\b(?:USD|EUR|GBP|CHF|ILS|RUB)\b|[$€£¥₪₽₹])|[0-9,.'’]{3,}\b)/gi }
|
|
433
462
|
],
|
|
434
463
|
pharma: [
|
|
435
464
|
{ type: 'ID', regex: /\b(?:Subject|Patient|Participant)\s+(?:ID|No)[:\s#]*[-A-Z0-9]{3,15}\b/gi },
|
|
@@ -588,7 +617,7 @@ const CORPORATE_SUFFIXES = new Set([
|
|
|
588
617
|
let NOT_NAME_WORDS = new Set([
|
|
589
618
|
'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',
|
|
590
619
|
'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',
|
|
591
|
-
'retirement', 'roth', 'ira', 'acct', 'custodial', 'brokerage', 'portfolio', 'annuity', 'dividend', 'distribution', 'reconciliation', 'withholding', 'remuneration', 'pin', 'lobby', 'passcode', 'code', 'gate', 'door', 'access', 'tenant', 'landlord', 'escrow', 'cadastral', 'claim', 'claims', 'adjuster', 'insured', 'carrier', 'indemnity', 'underwriter', 'underwriting', 'deductible', 'premium', 'settlement', 'loss', 'cargo', 'writers', 'guild', 'ghostwriter', 'actor', 'screenplay', 'audiovisual', 'cinematic', 'co-production', 'alumnus', 'alumni', 'registrar', 'transcript', 'diploma', 'orchestration', 'runtime', 'helpdesk', 'freshdesk', 'intercom', 'zendesk', 'investigational', 'pharmacovigilance', 'reaction', 'medication', 'manufacturing', 'lot', 'industrial', 'electronics', 'driver', 'drivers', 'license', 'licence', 'clearing', 'house', 'automated', 'central', 'securities', 'depository', 'arbitration', 'tribunal', 'instrument', 'infrastructure', 'talent', 'dossier',
|
|
620
|
+
'retirement', 'roth', 'ira', 'acct', 'custodial', 'brokerage', 'portfolio', 'annuity', 'dividend', 'distribution', 'reconciliation', 'withholding', 'remuneration', 'pin', 'lobby', 'passcode', 'code', 'gate', 'door', 'access', 'tenant', 'landlord', 'escrow', 'cadastral', 'claim', 'claims', 'adjuster', 'insured', 'carrier', 'indemnity', 'underwriter', 'underwriting', 'deductible', 'premium', 'settlement', 'loss', 'cargo', 'writers', 'guild', 'ghostwriter', 'actor', 'screenplay', 'audiovisual', 'cinematic', 'co-production', 'alumnus', 'alumni', 'registrar', 'transcript', 'diploma', 'orchestration', 'runtime', 'helpdesk', 'freshdesk', 'intercom', 'zendesk', 'investigational', 'pharmacovigilance', 'reaction', 'medication', 'manufacturing', 'lot', 'industrial', 'electronics', 'driver', 'drivers', 'license', 'licence', 'clearing', 'house', 'automated', 'central', 'securities', 'depository', 'arbitration', 'tribunal', 'instrument', 'infrastructure', 'talent', 'dossier', 'tel', 'phone', 'fax', 'mobile', 'cell', 'email', 'dpoa', 'poa',
|
|
592
621
|
// Grammatical & Sentence Starters
|
|
593
622
|
'the', 'a', 'an', 'this', 'that', 'these', 'those', 'my', 'your', 'his', 'her', 'their', 'our', 'its', 'it', 'he', 'she', 'they', 'we', 'i', 'you', 'who', 'whom', 'which', 'what', 'whose', 'why', 'how', 'when', 'where', 'with', 'for', 'from', 'by', 'to', 'at', 'in', 'on', 'of', 'about', 'as', 'into', 'through', 'during', 'before', 'after', 'above', 'below', 'and', 'but', 'or', 'so', 'yet', 'im', "i'm", "you're", "they're", "we're", "it's", "he's", "she's", "that's", "there's", "what's", "who's", "i've", "you've", "we've", "they've", "i'll", "you'll", "we'll", "they'll", "i'd", "you'd", "we'd", "they'd",
|
|
594
623
|
// Verbs, Auxiliaries, Commands & Imperatives
|
|
@@ -727,7 +756,7 @@ const PROFILE_JARGON = {
|
|
|
727
756
|
'insurance', 'bcbs', 'bronchitis', 'amoxicillin', 'penicillin', 'antibiotic', 'antibiotics', 'vital',
|
|
728
757
|
'vitals', 'bp', 'hr', 'bpm', 'mmhg', 'allergies', 'dosage', 'parenchyma', 'ventricles', 'fazekas',
|
|
729
758
|
'ischemia', 'lesion', 'cardiology', 'atherosclerotic', 'aortocoronary', 'bypass', 'graft', 'hyperlipidemia',
|
|
730
|
-
'diverticulitis', 'resection', 'colon', 'superbill', 'encounter', 'cpt', 'hcpcs', 'icd-10'
|
|
759
|
+
'diverticulitis', 'resection', 'colon', 'superbill', 'encounter', 'cpt', 'hcpcs', 'icd-10', 'pt'
|
|
731
760
|
],
|
|
732
761
|
realestate: [
|
|
733
762
|
'escrow', 'tenant', 'landlord', 'lease', 'mortgage', 'appraisal', 'broker', 'property', 'zoning',
|
|
@@ -744,7 +773,13 @@ const PROFILE_JARGON = {
|
|
|
744
773
|
'recitals', 'whereas', 'confidential', 'information', 'care', 'term', 'exhibit', 'party', 'parties',
|
|
745
774
|
'disclosing', 'receiving', 'witnesseth', 'standard', 'indemnification', 'liability', 'governing',
|
|
746
775
|
'law', 'docket', 'deponent', 'reporter', 'appearances', 'examination', 'services', 'scope', 'work',
|
|
747
|
-
'master', 'msa'
|
|
776
|
+
'master', 'msa',
|
|
777
|
+
'finra', 'sec', 'disciplinary', 'arbitration', 'proceeding', 'proceedings', 'dispute', 'resolution',
|
|
778
|
+
'escrow', 'claimant', 'respondent', 'petitioner', 'appellant', 'appellee', 'trustee', 'trustor',
|
|
779
|
+
'grantor', 'grantee', 'fiduciary', 'principal', 'agent', 'officer', 'counsel', 'lead', 'attorney',
|
|
780
|
+
'partner', 'associate', 'representative', 'broker', 'primary', 'privilege', 'privileged', 'doctrine',
|
|
781
|
+
'work-product', 'power', 'durable', 'evidence', 'rules', 'rule', 'statutory', 'disqualification',
|
|
782
|
+
'acquisition', 'file', 'securities', 'exchange'
|
|
748
783
|
],
|
|
749
784
|
academic: [
|
|
750
785
|
'institutional', 'review', 'board', 'irb', 'protocol', 'national', 'institutes', 'health', 'nih',
|
|
@@ -829,7 +864,7 @@ const PROFILE_JARGON = {
|
|
|
829
864
|
'underwriting': 'underwriting', 'lending': 'underwriting', 'mortgage': 'underwriting', 'loan': 'underwriting', 'income': 'underwriting', 'income_verification': 'underwriting', 'payroll': 'underwriting', 'w2': 'underwriting', 'paystub': 'underwriting',
|
|
830
865
|
'medical': 'medical', 'healthcare': 'medical', 'health': 'medical', 'pharma': 'pharma', 'hipaa': 'medical',
|
|
831
866
|
'engineering': 'engineering', 'dev': 'dev', 'api': 'dev', 'devops': 'engineering', 'tech': 'tech',
|
|
832
|
-
'finance': 'finance', 'bizops': 'bizops', 'sales': 'sales', 'wealthmgmt': 'wealthmgmt', 'wealth': 'wealthmgmt', 'insurance': 'insurance', 'accounting': 'accounting', 'pci': 'finance',
|
|
867
|
+
'finance': 'finance', 'financial': 'finance', 'bizops': 'bizops', 'sales': 'sales', 'wealthmgmt': 'wealthmgmt', 'wealth': 'wealthmgmt', 'insurance': 'insurance', 'accounting': 'accounting', 'pci': 'finance',
|
|
833
868
|
'legal': 'legal', 'compliance': 'compliance', 'ccpa': 'ccpa', 'gdpr': 'compliance', 'soc2': 'compliance', 'iso27001': 'compliance', 'nist': 'compliance', 'dpo': 'compliance', 'grc': 'compliance',
|
|
834
869
|
'hr': 'hr', 'security': 'security', 'marketing': 'marketing', 'support': 'support',
|
|
835
870
|
'realestate': 'realestate', 'academic': 'academic', 'agents': 'agents', 'ai_agents': 'agents', 'creative': 'creative', 'personal': 'personal'
|
|
@@ -847,16 +882,18 @@ const PROFILE_JARGON = {
|
|
|
847
882
|
}
|
|
848
883
|
|
|
849
884
|
function stitchOrphanedNameLines(text, profile) {
|
|
850
|
-
if (
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
text = text.replace(/
|
|
885
|
+
if (!text || !text.includes('\n')) return text;
|
|
886
|
+
const prof = (profile || 'general').toLowerCase();
|
|
887
|
+
if (prof === 'medical' || prof === 'general') {
|
|
888
|
+
text = text.replace(/(Patient\s+Name|Emergency\s+Contact|In\s+Case\s+of\s+Emergency|ICE(?:\s+Contact)?):\s*\r?\n+([A-Z\p{Lu}][A-Za-z0-9&.,'’ \t-]*?[A-Za-z0-9\p{Lu}\p{Ll}])(?=\r?\n|$)/gui, '$1: $2');
|
|
889
|
+
} else if (prof === 'legal') {
|
|
890
|
+
text = text.replace(/Defendant:\s*\r?\n+([A-Z][a-zA-Z]+\s[A-Z][a-zA-Z]+)/g, 'Defendant: $1');
|
|
854
891
|
}
|
|
855
892
|
return text;
|
|
856
893
|
}
|
|
857
894
|
|
|
858
895
|
function detectMatches(text, activeProfile = 'general', customRules = [], nlpNames = [], enabledEntities = null, mlEntities = []) {
|
|
859
|
-
let textToProcess = text.replace(/[\u200b\u200c\u200d\ufeff]/
|
|
896
|
+
let textToProcess = stitchOrphanedNameLines(text, activeProfile).replace(/[\u200b\u200c\u200d\ufeff\u00ad]/gi, '');
|
|
860
897
|
textToProcess = textToProcess.replace(/([a-z])(Email|Phone|Mobile|Tel|Address|IP|ID|URL|SSN|Date):/g, '$1 $2:');
|
|
861
898
|
|
|
862
899
|
let matches = [];
|
|
@@ -934,9 +971,9 @@ const PROFILE_JARGON = {
|
|
|
934
971
|
if (!val || NAME_STOP_LIST.has(val) || currentJargon.has(val) || NOT_NAME_WORDS.has(val)) continue;
|
|
935
972
|
|
|
936
973
|
if (rule.type === 'NAME') {
|
|
937
|
-
let words = val.split(/[ \t\xA0]+/);
|
|
974
|
+
let words = val.split(/[ \t\xA0\^]+/);
|
|
938
975
|
if (!rule.isContextName) {
|
|
939
|
-
let origWords = matchedText.split(/[ \t\xA0]+/);
|
|
976
|
+
let origWords = matchedText.split(/[ \t\xA0\^]+/);
|
|
940
977
|
while (words.length > 2 && (currentJargon.has(words[0]) || (words[0].length > 1 && NOT_NAME_WORDS.has(words[0])) || (words[0].replace(/[^\p{L}]/gu, '').length > 1 && NOT_NAME_WORDS.has(words[0].replace(/[^\p{L}]/gu, ''))))) {
|
|
941
978
|
origWords.shift();
|
|
942
979
|
words.shift();
|
|
@@ -956,7 +993,7 @@ const PROFILE_JARGON = {
|
|
|
956
993
|
if (cleanW.length <= 1) return false;
|
|
957
994
|
return currentJargon.has(w) || NOT_NAME_WORDS.has(w) || NOT_NAME_WORDS.has(cleanW) || NAME_STOP_LIST.has(w) || NAME_STOP_LIST.has(cleanW);
|
|
958
995
|
}) ||
|
|
959
|
-
val.split(/[ \t\xA0
|
|
996
|
+
val.split(/[ \t\xA0\^\-]+/).some(sw => {
|
|
960
997
|
const cleanSW = sw.replace(/[^\p{L}]/gu, '');
|
|
961
998
|
if (cleanSW.length <= 1) return false;
|
|
962
999
|
return currentJargon.has(sw) || NOT_NAME_WORDS.has(sw) || NOT_NAME_WORDS.has(cleanSW) || NAME_STOP_LIST.has(sw) || NAME_STOP_LIST.has(cleanSW);
|
|
@@ -974,6 +1011,7 @@ const PROFILE_JARGON = {
|
|
|
974
1011
|
|
|
975
1012
|
if (rule.isContextName && words.length > 0 && words.some(w => {
|
|
976
1013
|
const cleanW = w.replace(/[^\p{L}]/gu, '');
|
|
1014
|
+
if (cleanW.length <= 1) return false;
|
|
977
1015
|
if (rule.isCorporateName && (CORPORATE_SUFFIXES.has(cleanW) || US_STATES_SET.has(cleanW))) {
|
|
978
1016
|
return false;
|
|
979
1017
|
}
|
|
@@ -1168,7 +1206,12 @@ const PROFILE_JARGON = {
|
|
|
1168
1206
|
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, '');
|
|
1169
1207
|
cleaned = cleaned.replace(/^\s*Edit\s*\n+/i, "");
|
|
1170
1208
|
cleaned = cleaned.replace(/\s*\bEdit\s+in\s+a\s+page\b\s*$/i, "");
|
|
1171
|
-
// 3. Strip
|
|
1209
|
+
// 3. Strip echoed system instructions and PrivacyScrubber pseudonymization disclaimers
|
|
1210
|
+
cleaned = cleaned.replace(/^\s*(?:Understood|Acknowledged|Got it|Sure|OK|Okay|Note taken|Understood[.,!])[^\n]*\n+/i, "");
|
|
1211
|
+
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");
|
|
1212
|
+
cleaned = cleaned.replace(/(?:^|\n*)\s*\[(?:Privacy Note|Context: identifiers|Privacy Scrubber Mode|SYSTEM INSTRUCTION: DATA PRIVACY MODE)[^\]]*\]\s*(?:\n*|$)/gi, "\n\n");
|
|
1213
|
+
cleaned = cleaned.replace(/(?:^|\n*)\s*(?:Direct identifiers in this prompt were pseudonymized|I will retain all bracketed tokens)[^\n]*(?:\n*|$)/gi, "\n\n");
|
|
1214
|
+
// 4. Strip stray leading colons, semicolons, or separators left by stripped icons/artifact headers
|
|
1172
1215
|
cleaned = cleaned.replace(/^[:;|\-\—\–]+(?=\n|$)/, "");
|
|
1173
1216
|
cleaned = cleaned.replace(/^[:;]+\s*/, "");
|
|
1174
1217
|
return cleaned.trim();
|
package/scrubber-core.cjs
CHANGED
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
function stitchOrphanedNameLines(text, profile) {
|
|
39
39
|
if (!text || !text.includes('\n')) return text;
|
|
40
40
|
const prof = (profile || 'general').toLowerCase();
|
|
41
|
-
if (prof === 'medical') {
|
|
42
|
-
text = text.replace(/Patient
|
|
41
|
+
if (prof === 'medical' || prof === 'general') {
|
|
42
|
+
text = text.replace(/(Patient\s+Name|Emergency\s+Contact|In\s+Case\s+of\s+Emergency|ICE(?:\s+Contact)?):\s*\r?\n+([A-Z\p{Lu}][A-Za-z0-9&.,'’ \t-]*?[A-Za-z0-9\p{Lu}\p{Ll}])(?=\r?\n|$)/gui, '$1: $2');
|
|
43
43
|
} else if (prof === 'legal') {
|
|
44
|
-
text = text.replace(/Defendant:\s*\n+([A-Z][a-zA-Z]+\s[A-Z][a-zA-Z]+)/g, 'Defendant: $1');
|
|
44
|
+
text = text.replace(/Defendant:\s*\r?\n+([A-Z][a-zA-Z]+\s[A-Z][a-zA-Z]+)/g, 'Defendant: $1');
|
|
45
45
|
}
|
|
46
46
|
return text;
|
|
47
47
|
}
|
|
@@ -66,7 +66,7 @@ function scrubText(text, customRules = [], tokenLabelMap = {}, profile = 'Genera
|
|
|
66
66
|
|
|
67
67
|
// Protect system prompt from being scrubbed or counted
|
|
68
68
|
let extractedSystemPrompt = "";
|
|
69
|
-
let textToProcess = text.replace(/[\u200b\u200c\u200d\ufeff]/
|
|
69
|
+
let textToProcess = text.replace(/[\u200b\u200c\u200d\ufeff\u00ad]/gi, '');
|
|
70
70
|
|
|
71
71
|
const isSpecialized = profile && profile.toLowerCase() !== 'general';
|
|
72
72
|
|
|
@@ -1116,7 +1116,7 @@ function hydrateRegex(rule) {
|
|
|
1116
1116
|
}
|
|
1117
1117
|
});
|
|
1118
1118
|
if (remaskedCount > 0) {
|
|
1119
|
-
if (typeof showInPageToast === 'function') showInPageToast(
|
|
1119
|
+
if (typeof showInPageToast === 'function') showInPageToast('Re-masked in DOM. Original tokens restored.', "info");
|
|
1120
1120
|
document.querySelectorAll('.ps-toolbar-reveal, [id$="-reveal"]').forEach(b => {
|
|
1121
1121
|
b.setAttribute('title', 'Reveal Original Data (Decrypted Locally)');
|
|
1122
1122
|
b.classList.remove('ps-revealed-active');
|
|
@@ -1184,7 +1184,7 @@ function hydrateRegex(rule) {
|
|
|
1184
1184
|
});
|
|
1185
1185
|
|
|
1186
1186
|
if (restoredCount > 0) {
|
|
1187
|
-
if (typeof showInPageToast === 'function') showInPageToast(
|
|
1187
|
+
if (typeof showInPageToast === 'function') showInPageToast('✓ Decrypted Locally in RAM. Safe to copy.', "success");
|
|
1188
1188
|
document.querySelectorAll('.ps-toolbar-reveal, [id$="-reveal"]').forEach(b => {
|
|
1189
1189
|
b.setAttribute('title', 'Hide / Re-mask Protected Data in DOM');
|
|
1190
1190
|
b.classList.add('ps-revealed-active');
|
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/moxno/privacyscrubber-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "2.1
|
|
9
|
+
"version": "2.2.1",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@privacyscrubber/mcp-server",
|
|
14
|
-
"version": "2.1
|
|
14
|
+
"version": "2.2.1",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -26,4 +26,4 @@
|
|
|
26
26
|
]
|
|
27
27
|
}
|
|
28
28
|
]
|
|
29
|
-
}
|
|
29
|
+
}
|