@himorishige/noren-core
Version:
Core PII detection, masking, and tokenization library built on Web Standards
2 lines (1 loc) • 33.5 kB
JavaScript
const e={email:new Set(["example.com","example.net","example.org","example.edu","localhost","localhost.localdomain","invalid","test","local","noreply@","no-reply@","donotreply@","do-not-reply@"]),ipv4:new Set(["127.0.0.1","0.0.0.0","192.0.2.0/24","198.51.100.0/24","203.0.113.0/24","10.0.0.0/8","172.16.0.0/12","192.168.0.0/16"]),ipv6:new Set(["::1","::","2001:db8::/32","fc00::/7","fd00::/8","fe80::/10"]),phone:new Set(["555-0100","555-0199","0000000000","1111111111","1234567890","9999999999"]),credit_card:new Set(["4242424242424242","4111111111111111","5555555555554444","378282246310005","6011111111111117"])};class t{environment;allowlist;denylist;allowPrivateIPs;allowTestPatterns;constructor(e={}){this.environment=e.environment??"production",this.allowPrivateIPs=e.allowPrivateIPs??"production"!==this.environment,this.allowTestPatterns=e.allowTestPatterns??"production"!==this.environment,this.allowlist=this.initializeAllowlist(e.customAllowlist),this.denylist=e.customDenylist??new Map}initializeAllowlist(t){const n=new Map;if("production"!==this.environment)for(const[t,s]of Object.entries(e))n.set(t,new Set(s));else n.set("email",new Set(["noreply@","no-reply@","donotreply@","do-not-reply@"]));if(t)for(const[e,s]of t){const t=n.get(e)??new Set;for(const e of s)t.add(e);n.set(e,t)}return n}isAllowed(e,t,n){const s=e.toLowerCase().trim(),r=this.denylist.get(t);if(r)for(const e of r)if(this.matchesPattern(s,e,t))return!1;if(n&&this.isInCommentOrDocumentation(n,e))return!0;const i=this.allowlist.get(t);if(i)for(const e of i)if(this.matchesPattern(s,e,t))return!0;return!("ipv4"!==t||!this.allowPrivateIPs||!this.isPrivateIPv4(e))||!("ipv6"!==t||!this.allowPrivateIPs||!this.isPrivateIPv6(e))||!(!this.allowTestPatterns||!this.isTestPattern(s,t))}matchesPattern(e,t,n){return t.includes("/")?this.matchesCIDR(e,t):t.endsWith("@")?e.startsWith(t):"email"===n?t===e||(t.includes("@")?e===t.toLowerCase():e.endsWith(`@${t}`)||e.endsWith(`.${t}`)):e===t||e.includes(t)}matchesCIDR(e,t){const[n]=t.split("/");return e.startsWith(n.split(".").slice(0,-1).join("."))}isPrivateIPv4(e){const t=e.split(".").map(Number);return 4===t.length&&(10===t[0]||172===t[0]&&t[1]>=16&&t[1]<=31||192===t[0]&&168===t[1]||127===t[0])}isPrivateIPv6(e){const t=e.toLowerCase();return"::1"===t||"::"===t||!!t.startsWith("fe80:")||!(!t.startsWith("fc")&&!t.startsWith("fd"))||!!t.startsWith("2001:db8:")}isInCommentOrDocumentation(e,t){const n=e.indexOf(t);if(-1===n)return!1;const s=e.substring(0,n),r=e.substring(n+t.length),i=Math.max(0,s.lastIndexOf("\n")),a=r.indexOf("\n"),o=e.substring(i,-1===a?e.length:n+t.length+a),c=[/^\s*\/\//,/\/\/[^"]*$/,/^\s*\/\*[\s\S]*?\*\//,/^\s*#/,/^\s*<!--[\s\S]*?-->/,/^\s*\*\//,/^\s*\*/];for(const e of c)if(e.test(o.trim()))return!0;const l=[/\bexample\s*[:]\s*/i,/\bdemo\s*[:]\s*/i,/\bsample\s*[:]\s*/i,/\btest\s*[:]\s*/i,/\bplaceholder\s*[:]\s*/i,/\be\.g\.\s*/i,/\bfor\s+example\b/i];for(const e of l)if(e.test(o))return!0;const d=o.substring(0,n-i),u=o.substring(n-i+t.length);if(d.includes("`")&&u.includes("`")){const e=(d.match(/`/g)||[]).length,t=(u.match(/`/g)||[]).length;if(e%2==1&&t%2==1)return!0}if(/^\s*[A-Z_]+=/.test(o)){const e=["test","demo","sample","example","fake"],t=o.toLowerCase();for(const n of e)if(t.includes(n))return!0}return!1}isTestPattern(e,t){if("email"===t){if(e.includes("@example.com")||e.includes("@example.org")||e.includes("@example.net"))return!0;if((e.startsWith("test@")||e.startsWith("dummy@"))&&e.includes("example."))return!0;if(e.includes("noreply")||e.includes("no-reply"))return!0}if("credit_card"===t){const t=["4242424242424242","4111111111111111","378282246310005","6011111111111117"],n=e.replace(/\D/g,"");if(t.includes(n))return!0;if("4242424242424242"===n)return!0}if("phone"===t){if(/^(\d)\1+$/.test(e.replace(/\D/g,"")))return!0;if("1234567890"===e.replace(/\D/g,""))return!0}return!1}addToAllowlist(e,t){const n=this.allowlist.get(e)??new Set;for(const e of t)n.add(e);this.allowlist.set(e,n)}addToDenylist(e,t){const n=this.denylist.get(e)??new Set;for(const e of t)n.add(e);this.denylist.set(e,n)}getConfig(){const e={};for(const[t,n]of this.allowlist)e[t]=Array.from(n);const t={};for(const[e,n]of this.denylist)t[e]=Array.from(n);return{environment:this.environment,allowPrivateIPs:this.allowPrivateIPs,allowTestPatterns:this.allowTestPatterns,allowlist:e,denylist:t}}}const n={strict:.5,balanced:.7,relaxed:.85};function s(e,t,n){const s=function(e,t,n){const s=Math.max(0,e.start-50),o=Math.min(t.length,e.end+50),c=t.slice(s,o).toLowerCase(),l=c.match(/\b\w+\b/g)||[];return{hasTestKeywords:["test","example","dummy","sample","demo","fake","mock"].some(e=>c.includes(e)),hasExampleKeywords:["example.com","example.org","localhost","invalid","placeholder"].some(e=>c.includes(e)),isInCodeBlock:r(t,e.start),isInComment:i(t,e.start),surroundingWords:l,patternComplexity:a(e.value),patternLength:e.value.length,hasValidFormat:!0,typeSpecific:{},...n}}(e,t,n),o=[];let c=.5;c+=function(e){switch(e){case"email":return.6;case"credit_card":return.7;default:return.4}}(e.type,e.value),o.push(`base-pattern-${e.type}`);const l=function(e){let t=0;const n=[];return e.hasTestKeywords&&(t-=.1,n.push("test-keywords-present")),e.hasExampleKeywords&&(t-=.15,n.push("example-keywords-present")),e.isInCodeBlock&&(t-=.2,n.push("in-code-block")),e.isInComment&&(t-=.2,n.push("in-comment")),e.patternLength>20?(t+=.1,n.push("long-pattern")):e.patternLength<5&&(t-=.1,n.push("short-pattern")),{adjustment:t,reasons:n}}(s);c+=l.adjustment,o.push(...l.reasons);const d=(u=e.type,h=e.value,"email"===u?function(e){let t=0;const n=[],s=e.toLowerCase();["example.com","example.org","test.com","localhost"].some(e=>s.endsWith(e))&&(t-=.4,n.push("test-domain")),(s.startsWith("noreply@")||s.startsWith("no-reply@"))&&(t-=.4,n.push("noreply-pattern"));const r=s.split(".").pop();return r&&r.length>=2&&r.length<=6&&/^[a-z]+$/.test(r)&&(t+=.2,n.push("valid-tld")),/admin@|test@|user@/.test(s)&&(t-=.2,n.push("generic-prefix")),{adjustment:t,reasons:n}}(h):{adjustment:0,reasons:[]});var u,h;return c+=d.adjustment,o.push(...d.reasons),{confidence:Math.max(0,Math.min(.99,c)),reasons:o,features:s}}function r(e,t){return(e.slice(0,t).match(/```/g)||[]).length%2==1}function i(e,t){const n=function(e,t){const n=e.slice(0,t),s=e.slice(t),r=n.lastIndexOf("\n")+1,i=s.indexOf("\n");return-1===i?e.slice(r):e.slice(r,t+i)}(e,t);return n.trim().startsWith("//")||n.trim().startsWith("#")}function a(e){let t=0;return/[a-z]/.test(e)&&t++,/[A-Z]/.test(e)&&t++,/\d/.test(e)&&t++,/[^a-zA-Z0-9]/.test(e)&&t++,t+=Math.min(e.length/20,2),t}function o(e,t,s){return e.filter(e=>void 0===e.confidence||function(e,t,s){return e>=(s??n[t])}(e.confidence,t,s))}const c=new Set(["4111111111111111","4012888888881881","4222222222222","4242424242424242","4000000000000002","4000000000000010","4000000000000028","4000000000000036","4000000000000044","4000000000000069","4000000000000101","4000000000000119","4000000000000127","4000000000000135","5555555555554444","5105105105105100","5200828282828210","5204230080000017","5204740009999999","5420923878724339","5555555555554444","378282246310005","371449635398431","378734493671000","347000000000000","6011111111111117","6011000990139424","6011981111111113","3530111333300000","3566002020360505","30569309025904","38520000023237","6759649826438453","6799990100000000019"]),l=new Set(Array.from(c).flatMap(e=>[e,e.replace(/[\s-]/g,""),e.replace(/(.{4})/g,"$1 ").trim(),e.replace(/(.{4})/g,"$1-").slice(0,-1)])),d={visa:{pattern:/^4/,lengths:[13,16,19]},mastercard:{pattern:/^(5[1-5]|2[2-7])/,lengths:[16]},amex:{pattern:/^3[47]/,lengths:[15]},discover:{pattern:/^(6011|65|64[4-9])/,lengths:[16,19]},jcb:{pattern:/^35(2[89]|[3-8][0-9])/,lengths:[16,17,18,19]},dinersclub:{pattern:/^3[0689]/,lengths:[14]},maestro:{pattern:/^(5018|5020|5038|6304|6759|676[1-3])/,lengths:[12,13,14,15,16,17,18,19]}},u=new Set(["example.com","example.net","example.org","example.edu","localhost","localhost.localdomain","invalid","test","local","test.com","test.net","test.org","testing.com","demo.com","sample.com","fake.com","dummy.com","placeholder.com","10minutemail.com","guerrillamail.com","mailinator.com","tempmail.org"]),h=new Set(["noreply","no-reply","donotreply","do-not-reply","admin","administrator","postmaster","webmaster","hostmaster","info","support","help","sales","marketing","contact","abuse","security","privacy","legal","compliance","billing","accounts","notifications","alerts","system","daemon","service","robot","bot","automated"]),f={email:new Set(["email","e-mail","mail","mailto","from","to","cc","bcc","sender","recipient","address","contact","reach","write","send","メール","メールアドレス","eメール","電子メール","電子郵便","送信者","受信者","宛先","アドレス","連絡先","問い合わせ先","送信","受信","返信","転送","配信","お知らせ","通知","件名","本文","添付","ファイル添付"]),credit_card:new Set(["card","credit","debit","visa","mastercard","amex","discover","jcb","maestro","cc","payment","billing","charge","transaction","exp","cvv","cvc","security","code","expiry","expiration","カード","クレカ","クレジットカード","デビットカード","デビカ","クレジット","信用カード","決済","支払","支払い","料金","有効期限","期限","セキュリティコード","セキュリティ番号","カード番号","カード情報","取引","会計","清算","精算","ビザ","マスター","アメックス","JCB","ジェーシービー","ショッピング","購入","買い物","お支払い","ご請求"])},p=new Set(["version","ver","v","release","rel","build","changelog","history","sample","example","demo","test","testing","dummy","fake","mock","placeholder","template","sandbox","trial","documentation","docs","spec","specification","tutorial","guide","readme","license","copyright","author","manual","uuid","guid","hash","checksum","digest","signature","token","key","secret","certificate","fingerprint","identifier","id","テスト","サンプル","例","見本","デモ","試用","試験","ドキュメント","仕様","仕様書","ガイド","ガイドライン","マニュアル","リリース","バージョン","版","更新","アップデート","開発","開発用","開発環境","テスト環境","検証","実験","プレースホルダー","プレースホルダ","ダミー","偽","フェイク","仮","一時","仮データ","テストデータ","サンプルデータ"]),m={fast:{description:"Maximum performance, minimal false positive filtering",contextRequired:!1,excludeTestData:!1,excludePrivateNetworks:!1,brandValidation:!1},balanced:{description:"Good balance of performance and accuracy",contextRequired:!1,excludeTestData:!0,excludePrivateNetworks:!1,brandValidation:!0},strict:{description:"Maximum accuracy, stricter validation",contextRequired:!0,excludeTestData:!0,excludePrivateNetworks:!0,brandValidation:!0}};function g(e,t,n=24){let s=1;const r=[],i=[],a=[],o=e.toLowerCase(),c=f[t]||new Set;for(const e of c){let t=!1;t=/[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/.test(e)?o.includes(e.toLowerCase()):new RegExp(`\\b${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"i").test(o),t&&(s+=.2,r.push(e),a.push(`positive_keyword:${e}`))}for(const e of p){let t=!1;t=/[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/.test(e)?o.includes(e.toLowerCase()):new RegExp(`\\b${e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\b`,"i").test(o),t&&(s-=.4,i.push(e),a.push(`negative_keyword:${e}`))}s=function(e,t,n,s){switch(n){case"credit_card":return function(e,t,n){return/\b(payment|transaction|purchase|billing|checkout|order)\b/.test(t)&&(e+=.3,n.push("payment_context")),/\b(exp|expir|cvv|cvc|security\s+code)\b/.test(t)&&(e+=.4,n.push("card_details_context")),/\b(example|sample|demo|test|dummy)\b/.test(t)&&(e-=.5,n.push("test_context")),/\b(id|identifier|user|customer|account)\s*:?\s*\d+/.test(t)&&(e-=.2,n.push("identifier_context")),/\b(insert|update|select|database|log|debug)\b/.test(t)&&(e-=.3,n.push("database_context")),/\b(random|number|digits?|value|data|string)\b/.test(t)&&(e-=.15,n.push("generic_number_context")),e}(e,t,s);case"ipv4":case"ipv6":return function(e,t,n){return/\b(connect|connection|server|client|endpoint|gateway|router)\b/.test(t)&&(e+=.3,n.push("network_context")),/\b(from|to|src|dst|source|destination)\b/.test(t)&&(e+=.2,n.push("direction_context")),/\b(ping|traceroute|nslookup|dig|curl|wget|ssh|telnet|ftp)\b/.test(t)&&(e+=.4,n.push("network_tool_context")),/\b(version|release|build|package)\b/.test(t)&&(e-=.4,n.push("version_context")),/\b(date|time|timestamp|year|month|day)\b/.test(t)&&(e-=.3,n.push("date_context")),/\b(config|conf|settings|properties|ini|yaml|json|xml)\b/.test(t)&&(e+=.2,n.push("config_context")),e}(e,t,s);case"email":return function(e,t,n){return/\b(send|sent|receive|forward|reply|contact|reach)\b/.test(t)&&(e+=.3,n.push("communication_context")),/\b(from|to|cc|bcc|subject|message|mail)\b/.test(t)&&(e+=.4,n.push("email_headers_context")),/\b(noreply|no-reply|donotreply|system|daemon|automated)\b/.test(t)&&(e-=.5,n.push("automated_email_context")),/\b(example|test|demo|placeholder)\b/.test(t)&&(e-=.4,n.push("example_context")),/\b(domain|dns|mx|record|zone)\b/.test(t)&&(e-=.2,n.push("dns_context")),e}(e,t,s);case"phone_e164":return function(e,t,n){return/\b(call|dial|ring|mobile|cell|landline|extension|ext)\b/.test(t)&&(e+=.3,n.push("phone_action_context")),/\b(contact|reach|emergency|support|help|service)\b/.test(t)&&(e+=.2,n.push("contact_context")),/\b(test|example|dummy|sample|fake)\b/.test(t)&&(e-=.4,n.push("test_number_context")),/\b(id|identifier|code|reference|serial)\b/.test(t)&&(e-=.2,n.push("identifier_context")),e}(e,t,s);case"mac":return function(e,t,n){return/\b(ethernet|ether|nic|interface|adapter|card)\b/.test(t)&&(e+=.4,n.push("network_hardware_context")),/\b(ifconfig|arp|bridge|switch|router|gateway)\b/.test(t)&&(e+=.3,n.push("network_config_context")),/\b(uuid|guid|hash|checksum|signature|key)\b/.test(t)&&(e-=.5,n.push("identifier_hash_context")),/\b(bluetooth|bt|wireless|wifi)\b/.test(t)&&(e+=.2,n.push("wireless_context")),e}(e,t,s);default:return e}}(s,o,t,a);const l=r.length+i.length,d=Math.min(.95,.5+.1*l);return{score:Math.max(0,s),positiveMatches:r,negativeMatches:i,confidence:d,reasoning:a}}function y(e,t,n,s=24){const r=Math.max(0,t-s),i=Math.min(e.length,n+s);return e.slice(r,t)+e.slice(n,i)}function b(e,t,n="balanced"){const s={fast:{credit_card:.5,email:.3,ipv4:.3,ipv6:.3,phone_e164:.5,mac:.4,default:.3},balanced:{credit_card:1.1,email:.8,ipv4:.5,ipv6:.5,phone_e164:.8,mac:.6,default:.5},strict:{credit_card:1.5,email:1,ipv4:1,ipv6:1,phone_e164:1.2,mac:1,default:1}},r=s[n][t]||s[n].default;return e.score>=r}const v={email:new Set(["email","mail","contact","e_mail","メール","mailaddress"]),phone:new Set(["phone","tel","mobile","telephone","電話","携帯","cellphone"]),address:new Set(["address","addr","住所","所在地","location","street"]),credit_card:new Set(["card","card_number","cc","cardnum","カード番号","creditcard"]),ssn:new Set(["ssn","social_security","social","socialsecurity"]),name:new Set(["name","fullname","first_name","last_name","氏名","名前","firstname","lastname"]),id:new Set(["id","user_id","customer_id","personal_id","identification"]),birthday:new Set(["birthday","birth_date","dob","date_of_birth","生年月日","birthdate"])};class w{currentPath=[];hits=[];arrayIndices=[];detectInJson(e,t){this.hits=[],this.currentPath=[],this.arrayIndices=[];try{const n=JSON.parse(e);return this.traverseObject(n,t,0,e),{hits:this.hits,isValidJson:!0,fallbackToText:!1}}catch(n){return this.tryNDJSON(e,t)?{hits:this.hits,isValidJson:!0,fallbackToText:!1}:{hits:[],isValidJson:!1,fallbackToText:!0}}}tryNDJSON(e,t){const n=e.split("\n").filter(e=>e.trim());for(let s=0;s<n.length;s++)try{const r=JSON.parse(n[s]);this.currentPath=[`[${s}]`],this.arrayIndices=[s],this.traverseObject(r,t,0,e),this.currentPath=[],this.arrayIndices=[]}catch(e){return!1}return!0}traverseObject(e,t,n=0,s){n>10||(Array.isArray(e)?this.traverseArray(e,t,n,s):e&&"object"==typeof e&&this.traverseObjectProperties(e,t,n,s))}traverseArray(e,t,n,s){for(let r=0;r<e.length;r++)this.currentPath.push(`[${r}]`),this.arrayIndices.push(r),this.traverseObject(e[r],t,n+1,s),this.currentPath.pop(),this.arrayIndices.pop()}traverseObjectProperties(e,t,n,s){for(const[r,i]of Object.entries(e))this.currentPath.push(r),"string"==typeof i&&i.length>0?this.checkStringValue(r,i,t,s):i&&"object"==typeof i&&this.traverseObject(i,t,n+1,s),this.currentPath.pop()}checkStringValue(e,t,n,s){const r=e.toLowerCase(),i=this.getJsonPath(),{start:a,end:o}=this.findValuePosition(t,s||JSON.stringify(t)),c=this.detectPiiTypeFromKey(r);c?this.addJsonHit({type:c,value:t,jsonPath:i,keyName:e,confidence:.9,risk:this.getRiskLevel(c),reasons:["json_key_match",`key_pattern_${c}`],features:{keyBased:!0,detectedFromKey:e},start:a,end:o}):this.detectInStringValue(t,e,i,n,a,o)}detectPiiTypeFromKey(e){for(const[t,n]of Object.entries(v))if(n.has(e))return"phone"===t?"phone_e164":t;return e.includes("email")||e.includes("mail")?"email":e.includes("phone")||e.includes("tel")?"phone_e164":e.includes("card")||e.includes("credit")?"credit_card":e.includes("address")||e.includes("addr")?"address":null}detectInStringValue(e,t,n,s,r=0,i=0){s.hasCtx,s.canPush,this.basicPatternMatch(e,t,n,["json_content_match"],r,i)}basicPatternMatch(e,t,n,s=[],r=0,i=0){const a=e.matchAll(/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g);for(const e of a)this.addJsonHit({type:"email",value:e[0],jsonPath:n,keyName:t,confidence:.8,risk:"medium",reasons:[...s,"json_email_pattern","regex_match"],features:{keyBased:!1,foundInKey:t},start:r,end:i});const o=e.matchAll(/\b(?:\+?1[-.\s]?)?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}\b/g);for(const e of o)this.addJsonHit({type:"phone_e164",value:e[0],jsonPath:n,keyName:t,confidence:.7,risk:"medium",reasons:[...s,"json_phone_pattern","regex_match"],features:{keyBased:!1,foundInKey:t},start:r,end:i})}addJsonHit(e){this.hits.push({type:e.type,start:e.start||0,end:e.end||e.value.length,value:e.value,risk:e.risk,confidence:e.confidence,reasons:e.reasons,features:e.features,jsonPath:e.jsonPath,keyName:e.keyName})}getJsonPath(){return 0===this.currentPath.length?"$":`$.${this.currentPath.join(".")}`}findValuePosition(e,t){const n=t.indexOf(e);return n>=0?{start:n,end:n+e.length}:{start:0,end:e.length}}getRiskLevel(e){switch(e){case"credit_card":case"ssn":return"high";case"email":case"phone_e164":case"name":return"medium";default:return"low"}}}function x(){return new w}const _=new Map;function k(){_.clear()}function S(e){const t=[],n=e.split("\n").filter(e=>e.trim());for(const e of n)try{const n=JSON.parse(e.trim());P(n)&&t.push(n)}catch{}return t}function P(e){if(!e||"object"!=typeof e)return!1;const t=e;return"2.0"===t.jsonrpc&&("string"==typeof t.method||"result"in t||"error"in t)}function C(e){const t=[];if(e.params){const n=JSON.stringify(e.params);t.push(n)}if(e.result){const n=JSON.stringify(e.result);t.push(n)}if(e.error?.data){const n=JSON.stringify(e.error.data);t.push(n)}return t}async function A(e,t){const{registry:n,policy:s={}}=t,r=JSON.parse(JSON.stringify(e));if(r.params){const e=JSON.stringify(r.params),t=await redactText(n,e,s);try{r.params=JSON.parse(t)}catch{r.params=t}}if(r.result){const e=JSON.stringify(r.result),t=await redactText(n,e,s);try{r.result=JSON.parse(t)}catch{r.result=t}}if(r.error?.data){const e=JSON.stringify(r.error.data),t=await redactText(n,e,s);try{r.error.data=JSON.parse(t)}catch{r.error.data=t}}return r}function O(e){const{registry:t,policy:n={},lineBufferSize:s=65536}=e;let r="";const i=new TextDecoder,a=new TextEncoder;return new TransformStream({async transform(e,t){const n=i.decode(e,{stream:!0});r+=n;const c=r.split("\n");r=c.pop()||"";for(const e of c)e.trim()?await o(e.trim(),t):t.enqueue(a.encode("\n"));r.length>s&&(await o(r,t),r="")},async flush(e){r.trim()&&await o(r.trim(),e)}});async function o(e,s){try{const r=JSON.parse(e);if(P(r)){const e=await A(r,{registry:t,policy:n}),i=`${JSON.stringify(e)}\n`;s.enqueue(a.encode(i))}else{const r=await redactText(t,e,n);s.enqueue(a.encode(`${r}\n`))}}catch{const r=await redactText(t,e,n);s.enqueue(a.encode(`${r}\n`))}}}function j(e){return e.includes('"jsonrpc":"2.0"')||e.includes('"jsonrpc": "2.0"')}function I(e){return e.method||null}function T(e){return e.error?"error":e.method&&void 0!==e.id?"request":e.method&&void 0===e.id?"notification":"result"in e||"error"in e?"response":"request"}class M{pool=[];maxSize=20;clearCounter=0;acquire(e,t,n,s,r,i){const a=this.pool.pop();return a?(a.type=e,a.start=t,a.end=n,a.value=s,a.risk=r,a.priority=i,a):{type:e,start:t,end:n,value:s,risk:r,priority:i}}releaseOne(e){this.release([e])}release(e){for(const t of e)this.pool.length<this.maxSize&&(t.value="",t.priority=void 0,t.confidence=void 0,t.reasons=void 0,t.features=void 0,this.pool.push(t));++this.clearCounter>=100&&(this.pool.length=0,this.clearCounter=0)}releaseRange(e,t){const n=e.slice(0,Math.min(t,e.length));this.release(n)}clear(){this.pool.length=0,this.clearCounter=0}}const $=new M,D=/[\u2212\u2010-\u2015\u30FC]/g,J=/\u3000/g,N=/[ \t ]+/g,E=new RegExp(["(?<![A-Z0-9._%+-/])([A-Z0-9._%+-]{1,64}@[A-Z0-9.-]{1,253}\\.[A-Z]{2,63})(?![\\w])","|(?<![\\d])\\b((?:\\d[ -]?){12,18}\\d)\\b(?![\\d])"].join(""),"gi"),L=[{type:"email",risk:"medium"},{type:"credit_card",risk:"high"}];function H(e){let t=0,n=!1;for(let s=e.length-1;s>=0;s--){let r=e.charCodeAt(s)-48;if(r<0||r>9)return!1;n&&(r*=2,r>9&&(r-=9)),t+=r,n=!n}return t%10==0}async function z(e){if(e instanceof CryptoKey)return e;if("string"!=typeof e||e.length<32)throw new Error("HMAC key must be at least 32 characters long");const t=(new TextEncoder).encode(e);return crypto.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign"])}async function q(e,t){const n=(new TextEncoder).encode(e),s=await crypto.subtle.sign("HMAC",t,n);return btoa(String.fromCharCode(...new Uint8Array(s))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}const K=1e7;function F(e,t={}){const{window:n=96,policy:s={}}=t,r=new TextDecoder,i=new TextEncoder;let a="",o=!1;return new TransformStream({async transform(t,c){if(function(e){const t=Math.min(e.length,512);let n=0,s=0;for(let r=0;r<t;r++){const t=e[r];if(0===t&&(n++,n>=3))return!0;if(t<32&&9!==t&&10!==t&&13!==t&&(s++,s>=8))return!0}return n/t>.02||s/t>.05}(t)){if(a&&!o){const t=await redactText(e,a,s);c.enqueue(i.encode(t)),a=""}c.enqueue(t)}else try{const l=r.decode(t,{stream:!0}),d=a+l,u=Math.max(0,d.length-n),h=d.slice(0,u);if(h){const t=await redactText(e,h,s);c.enqueue(i.encode(t))}a=d.slice(u),o=!1}catch(n){if(a&&!o){const t=await redactText(e,a,s);c.enqueue(i.encode(t)),a=""}c.enqueue(t)}},async flush(t){if(a&&!o){const n=await redactText(e,a,s);t.enqueue(i.encode(n))}}})}function W(e,t){return g(e,t).score}function V(e,t,n){if(!e||"string"!=typeof e)return{valid:!1,confidence:0,reason:"invalid_input",metadata:{error:"Candidate must be a non-empty string"}};if(e.length>1e3)return{valid:!1,confidence:0,reason:"candidate_too_long",metadata:{length:e.length,maxLength:1e3}};try{switch(t){case"credit_card":return function(e,t){const n=e.replace(/[\s-]/g,""),s=m[t.strictness];if(n.length<13||n.length>19)return{valid:!1,confidence:0,reason:"invalid_length"};if(!H(n))return{valid:!1,confidence:0,reason:"luhn_failed"};if(s.excludeTestData&&l.has(n))return{valid:!1,confidence:0,reason:"test_number"};let r=null,i=!1;if(s.brandValidation){for(const[e,t]of Object.entries(d))if(t.pattern.test(n)){r=e,i=t.lengths.includes(n.length);break}if(!r)return{valid:!1,confidence:0,reason:"unknown_brand"};if(!i)return{valid:!1,confidence:0,reason:"invalid_brand_length",metadata:{brand:r,length:n.length}}}if(/(\d)\1{3,}/.test(n))return{valid:!1,confidence:.1,reason:"repeated_digits"};if(/(?:0123|1234|2345|3456|4567|5678|6789|9876|8765|7654|6543|5432|4321|3210)/.test(n))return{valid:!1,confidence:.2,reason:"sequential_digits"};if(!/[\s-]/.test(e)&&16===n.length&&s.excludeTestData){const e=W(t.surroundingText,"credit_card"),n="strict"===t.strictness?2:1.2;if(e<n)return{valid:!1,confidence:.3,reason:"bare_digits_weak_context",metadata:{contextScore:e,threshold:n}}}return{valid:!0,confidence:r?.9:.7,reason:r?`valid_${r}`:"valid_unknown_brand",metadata:{brand:r,digits:n.length}}}(e,n);case"email":return function(e,t){const n=m[t.strictness],s=e.indexOf("@");if(-1===s)return{valid:!1,confidence:0,reason:"no_at_symbol"};const r=e.slice(0,s),i=e.slice(s+1);if(n.excludeTestData&&u.has(i.toLowerCase()))return{valid:!1,confidence:0,reason:"example_domain"};const a=r.toLowerCase().replace(/[.-]/g,"");if(h.has(a))return{valid:!1,confidence:.1,reason:"non_pii_prefix",metadata:{prefix:a}};const o=i.lastIndexOf(".");if(-1===o)return{valid:!1,confidence:0,reason:"no_tld"};const c=i.slice(o+1);if(c.length<2||c.length>24||!/^[a-z]+$/i.test(c))return{valid:!1,confidence:0,reason:"invalid_tld"};const l=W(t.surroundingText,"email");return{valid:!0,confidence:Math.min(.9,.6+.15*l),reason:"valid_email",metadata:{contextScore:l,domain:i,isExample:u.has(i.toLowerCase())}}}(e,n);default:return{valid:!0,confidence:.7,reason:"no_validation"}}}catch(n){return{valid:!1,confidence:0,reason:"validation_error",metadata:{error:n instanceof Error?n.message:"Unknown validation error",piiType:t,candidateLength:e.length}}}}function R(e,t,n){const s=V(e,t,n);s.metadata&&Object.entries(s.metadata).forEach(([e,t])=>{}),s.valid}function Z(e,t){const n=e.replace(/\D/g,"");return n.length>=13&&n.length<=19}function B(e,t){if(t){if("credit_card"===e.type)return`**** **** **** ${e.value.replace(/\D/g,"").slice(-4)}`;{const t=e.value.slice(-4);return`[REDACTED:${e.type}]${t}`}}return"phone_e164"===e.type?e.value.replace(/\d/g,"•"):`[REDACTED:${e.type}]`}const U={high:3,medium:2,low:1};function G(e,t){return e<t}function Q(e,t){const n=U[e.risk],s=U[t.risk];if(n!==s)return n>s;const r=e.end-e.start,i=t.end-t.start;return r!==i?r>i:e.start<t.start}class Registry{detectors=[];maskers=new Map;base;contextHintsSet;allowDenyManager;enableConfidenceScoring;enableJsonDetection;constructor(e){this.validateOptions(e);const{environment:n,allowDenyConfig:s,enableConfidenceScoring:r,enableJsonDetection:i,...a}=e;this.base=a,this.contextHintsSet=new Set(a.contextHints??[]),this.enableConfidenceScoring=r??!0,this.enableJsonDetection=i??!1,this.allowDenyManager=new t({environment:n??"production",...s})}validateOptions(e){if(e.validationStrictness&&!["fast","balanced","strict"].includes(e.validationStrictness))throw new Error(`Invalid validationStrictness: ${e.validationStrictness}. Must be 'fast', 'balanced', or 'strict'`);if(e.hmacKey&&"string"==typeof e.hmacKey&&e.hmacKey.length<32)throw new Error("HMAC key must be at least 32 characters long for security");if(e.contextHints&&!Array.isArray(e.contextHints))throw new Error("contextHints must be an array of strings");if(e.rules)for(const[t,n]of Object.entries(e.rules))if(n&&"object"==typeof n&&n.action&&!["mask","remove","tokenize"].includes(n.action))throw new Error(`Invalid action '${n.action}' for type '${t}'. Must be 'mask', 'remove', or 'tokenize'`);if(e.defaultAction&&!["mask","remove","tokenize"].includes(e.defaultAction))throw new Error(`Invalid defaultAction: ${e.defaultAction}. Must be 'mask', 'remove', or 'tokenize'`)}use(e=[],t={},n=[]){for(const t of e)this.detectors.push(t);this.detectors.sort((e,t)=>{const n=e.priority??0,s=t.priority??0;return G(n,s)?-1:G(s,n)?1:0});for(const[e,n]of Object.entries(t))this.maskers.set(e,n);if(n.length){for(const e of n)this.contextHintsSet.add(e);this.base.contextHints=Array.from(this.contextHintsSet)}}async useLazy(e,t){const n=await async function(e,t){if(_.has(e)){const t=_.get(e);if(t)return t}const n=(async()=>{const[e,n,s]=await Promise.all([t.detectors?.()??Promise.resolve([]),t.maskers?.()??Promise.resolve({}),t.contextHints?.()??Promise.resolve([])]);return{detectors:e,maskers:n,contextHints:s}})();return _.set(e,n),n}(e,t);this.use(n.detectors,n.maskers,n.contextHints)}getPolicy(){return this.base}maskerFor(e){return this.maskers.get(e)}tryJsonDetection(e,t){const n=x().detectInJson(e,t);if(n.isValidJson&&n.hits.length>0)for(const e of n.hits){const n={type:e.type,start:e.start,end:e.end,value:e.value,risk:e.risk,priority:-5,confidence:e.confidence,reasons:e.reasons,features:{...e.features,isJsonDetection:!0,jsonPath:e.jsonPath,keyName:e.keyName}};t.push(n)}}async detect(e,t=this.base.contextHints??[]){if("string"!=typeof e)throw new Error("Input must be a string");if(e.length>K)throw new Error(`Input too large: ${e.length} chars exceeds limit of 10000000`);const n=(e=>{if(e.length>0&&/^[\x20-\x7E]*$/.test(e)&&!/[ \t]{2,}/.test(e))return e;const t=e.normalize("NFKC").replace(D,"-").replace(J," ").replace(N," "),n=t.trim();return n.length>0?n:t})(e),r=[];let i=null,a=null;const c={src:n,hasCtx:e=>{const s=(null===i&&(i=n.toLowerCase()),i);if(!e){if(null===a){const e=t.length>0?t:Array.from(this.contextHintsSet);a=e.some(e=>s.includes(e.toLowerCase()))}return a}return e.some(e=>s.includes(e.toLowerCase()))},push:e=>{r.length>=200||r.push(e)},canPush:()=>r.length<200},l=this.base.validationStrictness??"fast";!function(e,t="balanced"){const{src:n}=e;if(!(n.length>K||function(e){if(0===e.length)return{hasEmailCandidate:!1,hasIPCandidate:!1,hasCreditCardCandidate:!1,hasPhoneCandidate:!1,hasMACCandidate:!1,skipExpensiveDetection:!0};const t=e.includes("@"),n=e.includes("."),s=e.includes(":"),r=e.includes("-"),i=/\d/.test(e),a=t&&n,o=i&&(n||s),c=s||r,l=e.includes("+")||i;return{hasEmailCandidate:a,hasIPCandidate:o,hasCreditCardCandidate:i,hasPhoneCandidate:l,hasMACCandidate:c,skipExpensiveDetection:!(a||o||i||l||c)}}(n).skipExpensiveDetection))for(const s of n.matchAll(E)){if(!e.canPush||!e.canPush())break;if(null!=s.index)for(let r=1;r<s.length;r++)if(s[r]){const i=L[r-1];let a=!0;const o=s[r],c=s.index;if("credit_card"===i.type&&(Z(o)&&H(o.replace(/[ -]/g,""))||(a=!1)),a&&"fast"!==t){const e=48,s=Math.max(0,c-e),r=Math.min(n.length,c+o.length+e),l={surroundingText:n.slice(s,r),strictness:t,originalIndex:c-s};try{V(o,i.type,l).valid||(a=!1)}catch(e){}}if(a){const s=$.acquire(i.type,c,c+o.length,o,i.risk);if("fast"!==t){const e={surroundingText:y(n,c,c+o.length),strictness:t,originalIndex:c},r=V(o,i.type,e);s.confidence=r.confidence}e.push(s)}break}}}(c,l),this.enableJsonDetection&&this.tryJsonDetection(n,c);for(let e=0;e<r.length;e++)void 0===r[e].priority&&(r[e].priority=10);for(const e of this.detectors){const t=c.push;c.push=s=>{if(void 0===s.priority&&(s.priority=e.priority??0),"fast"!==l)try{const e=48,r=Math.max(0,s.start-e),i=Math.min(n.length,s.end+e),a={surroundingText:n.slice(r,i),strictness:l,originalIndex:s.start-r};V(s.value,s.type,a).valid&&t(s)}catch(e){t(s)}else t(s)},await e.match(c),c.push=t}if(0===r.length)return{src:n,hits:[]};r.sort((e,t)=>e.start-t.start||t.end-t.start-(e.end-e.start));let d=0,u=-1;for(let e=0;e<r.length;e++){const t=r[e];if(t.start>=u)r[d]=t,u=t.end,d++;else{const e=r[d-1],n=t.priority??0,s=e.priority??0;if(G(n,s)){const n=e;r[d-1]=t,u=t.end,$.releaseOne(n)}else if(n===s)if(Q(t,e)){const n=e;r[d-1]=t,u=t.end,$.releaseOne(n)}else $.releaseOne(t);else $.releaseOne(t)}}const h=[];for(let e=0;e<d;e++){const t=r[e],s=100,i=Math.max(0,t.start-s),a=Math.min(n.length,t.end+s),o=n.slice(i,a);this.allowDenyManager.isAllowed(t.value,t.type,o)||h.push(t)}const f=[];for(const e of h)if(this.enableConfidenceScoring){const t=s(e,n),r={...e,confidence:t.confidence,reasons:t.reasons,features:{...e.features,...t.features}};f.push(r)}else f.push(e);const p=this.enableConfidenceScoring&&this.base.sensitivity?o(f,this.base.sensitivity,this.base.confidenceThreshold):f,m=new Array(p.length);for(let e=0;e<p.length;e++){const t=p[e];m[e]={type:t.type,start:t.start,end:t.end,value:t.value,risk:t.risk,priority:t.priority,confidence:t.confidence,reasons:t.reasons,features:t.features}}return d>0&&$.releaseRange(r,d),{src:n,hits:m}}}async function redactText(e,t,n={}){const s={...e.getPolicy(),...n},{src:r,hits:i}=await e.detect(t,s.contextHints);if(0===i.length)return r;const a=(Object.values(s.rules??{}).some(e=>"tokenize"===e?.action)||"tokenize"===s.defaultAction)&&s.hmacKey?await z(s.hmacKey):void 0,o=[];let c=0;for(const t of i){const n=s.rules?.[t.type]??{action:s.defaultAction??"mask"};t.start>c&&o.push(r.slice(c,t.start));let i=t.value;if("remove"===n.action)i="";else if("mask"===n.action)i=e.maskerFor(t.type)?.(t)??B(t,n.preserveLast4);else if("tokenize"===n.action){if(!a)throw new Error(`hmacKey is required for tokenize action on type ${t.type}`);i=`TKN_${String(t.type).toUpperCase()}_${await q(t.value,a)}`}""!==i&&o.push(i),c=t.end}return c<r.length&&o.push(r.slice(c)),o.join("")}export{t as AllowDenyManager,n as CONFIDENCE_THRESHOLDS,f as CONTEXT_KEYWORDS,M as HitPool,w as JSONDetector,p as NEGATIVE_CONTEXT_KEYWORDS,Registry,m as STRICTNESS_LEVELS,g as calculateContextScore,k as clearPluginCache,j as containsJsonRpcPattern,x as createJSONDetector,O as createMCPRedactionTransform,F as createRedactionTransform,R as debugValidation,I as extractMethodName,C as extractSensitiveContent,y as extractSurroundingText,o as filterByConfidence,T as getMessageType,q as hmacToken,z as importHmacKey,P as isValidJsonRpcMessage,b as meetsContextThreshold,S as parseJsonLines,A as redactJsonRpcMessage,redactText,V as validateCandidate};