spamscanner 7.0.2 → 7.0.3

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.
@@ -454,7 +454,7 @@ ${t}
454
454
 
455
455
  Classify the email between the markers. ${r?"Answer with one word: ham, spam, phishing, scam or malware.":"Reply with the JSON object only."}`,x={...Bte(e),...e.headers},A=r?1:e.maxTokens;switch(e.api){case"decision":return{url:Vl(e,e.endpoint),headers:x,body:{model:e.model,state:t,questions:{verdict:{type:"choice",instructions:`${e.instructions}${a}`,criteria:BA}}}};case"anthropic":return{url:Vl(e,"/messages"),headers:{"anthropic-version":"2023-06-01",...x},body:{model:e.model,max_tokens:e.maxTokens,system:n,messages:[{role:"user",content:o}],...e.temperature===void 0?{}:{temperature:e.temperature}}};case"ollama":return{url:Vl(e,"/api/chat"),headers:x,body:{model:e.model,stream:!1,think:e.think,messages:[{role:"system",content:n},{role:"user",content:o}],...r?{logprobs:!0,top_logprobs:pk}:e.json?{format:"json"}:{},options:{temperature:e.temperature??0,num_predict:A},...e.keepAlive===void 0?{}:{keep_alive:e.keepAlive}}};case"classifier":return{url:Vl(e,e.modelInPath?`/${e.model}`:""),headers:x,body:{inputs:t,truncate:!0}};case"openai":return{url:Vl(e,"/chat/completions"),headers:x,body:{model:e.model,messages:[{role:"system",content:n},{role:"user",content:o}],[e.maxTokensField]:A,...e.temperature===void 0?r?{temperature:0}:{}:{temperature:e.temperature},...r?{logprobs:!0,top_logprobs:pk,...e.local&&!e.think?{chat_template_kwargs:{enable_thinking:!1}}:{}}:e.json?{response_format:{type:"json_object"}}:{}}};default:throw new TypeError(`Unknown LLM API "${e.api}". Use openai, anthropic, ollama, classifier or decision`)}}function Tte(e,t){if(e==="anthropic")return(Array.isArray(t?.content)?t.content:[]).filter(r=>r.type==="text").map(r=>r.text).join("")||null;if(e==="ollama")return t?.message?.content??t?.response??null;let i=t?.choices?.[0]?.message;return i?.content||i?.reasoning_content||null}var fA=class{constructor(t={},i=Jl.default.env){this.config=dte(t,i),this.cacheSize=t.cacheSize??1e3,this.cache=new Map,this.concurrency=Math.max(1,t.concurrency??4),this.active=0,this.waiting=[]}async acquire(){if(this.active<this.concurrency){this.active++;return}await new Promise(t=>{this.waiting.push(t)})}release(){let t=this.waiting.shift();t?t():this.active--}method(){return this.generate?"generate":this.config.method}async ask(t,i){let a=i===this.config.method?this.config:{...this.config,method:i,systemPrompt:this.config.generatePrompt},{url:n,headers:r,body:s}=Nte(a,t);await this.acquire();let o;try{o=await W8("POST",n,s,{headers:r,timeout:a.timeout,maxResponseBytes:a.maxResponseBytes,ca:a.ca})}finally{this.release()}if(a.api==="classifier")return wte(o,a.labels);if(a.api==="decision")return Ite(o);if(i==="decision"){let x=Qte(a.api,o);return x&&Ste(x)}return Dte(Tte(a.api,o))}async classifyText(t){let i=(0,np.createHash)("sha256").update(t).digest("hex");if(this.cache.has(i)){let o=this.cache.get(i);return this.cache.delete(i),this.cache.set(i,o),{...o,cached:!0}}let a=Date.now(),n=this.method(),r=await this.ask(t,n);if(!r&&n==="decision"&&this.config.api!=="decision"&&(this.generate||=r===void 0,n="generate",r=await this.ask(t,n)),!r)throw new Error(`${this.config.name} returned no verdict`);let s={...r,method:n,provider:this.config.provider,model:this.config.model||null,cached:!1,time:Date.now()-a};return this.cacheSize>0&&(this.cache.set(i,s),this.cache.size>this.cacheSize&&this.cache.delete(this.cache.keys().next().value)),s}async classify(t,i={}){return this.classifyText(Cte(t,i,{maxInputChars:this.config.maxInputChars,redact:this.config.redact}))}};var a0=require("node:buffer"),QY=require("node:events"),SY=bt(require("node:net"),1);var Xd=require("node:buffer"),CY=bt(require("node:dns"),1),bY=require("node:util"),Nme=(0,bY.debuglog)("spamscanner:auth"),mY;async function Tme(){return globalThis.File||=Xd.File,mY||=await Promise.resolve().then(()=>bt(gY(),1)),mY}var Rme=new Set(["A","AAAA","MX","TXT","PTR","CNAME","NS","SOA","SRV"]);function _me(e=1e4,t){let i=new CY.default.promises.Resolver({timeout:Math.max(1,Math.floor(e/2)),tries:2});return Array.isArray(t)&&t.length>0&&i.setServers(t),async(a,n="A")=>{let r=String(n).toUpperCase();if(!Rme.has(r)){let o=new Error(`Unsupported DNS record type ${n}`);throw o.code="ENOTIMP",o}let s;try{return await Promise.race([i.resolve(a,r),new Promise((o,x)=>{s=setTimeout(()=>{let A=new Error(`DNS lookup of ${a} ${r} timed out`);A.code="ETIMEOUT",x(A)},e)})])}finally{clearTimeout(s)}}}function yY(){return{dkim:{status:{result:"none",comment:"not checked"},results:[],aligned:null},spf:{status:{result:"none",comment:"not checked"},domain:null},dmarc:{status:{result:"none",comment:"not checked"},policy:null,domain:null,p:null},arc:{status:{result:"none",comment:"not checked"}},bimi:{status:{result:"none",comment:"not checked"},location:null},receivedChain:[],headers:"",error:null}}function Ume(e){let t=(Array.isArray(e?.results)?e.results:[]).filter(s=>s?.status?.result!=="none"),i=t.filter(s=>s?.status?.result==="pass");if(i.length>0){let s=i.find(o=>o.status.aligned)?.status.aligned||null;return{status:{result:"pass",comment:i.map(o=>o.signingDomain).join(", ")},results:t,aligned:s}}if(t.length===0)return{status:{result:"none",comment:"no signature"},results:t,aligned:null};let a=t.some(s=>s?.status?.result==="temperror"),n=t.every(s=>["neutral","policy","none"].includes(s?.status?.result)),r="fail";return a?r="temperror":n&&(r="neutral"),{status:{result:r,comment:t.map(s=>`${s.signingDomain||"?"}: ${s?.status?.comment||s?.status?.result||"invalid"}`).join("; ")},results:t,aligned:null}}function vb(e={}){let t=yY();return{dkim:Ume(e.dkim),spf:{status:e.spf?.status||t.spf.status,domain:e.spf?.domain||null},dmarc:{status:e.dmarc?.status||t.dmarc.status,policy:e.dmarc?.policy||null,domain:e.dmarc?.domain||null,p:e.dmarc?.p||null},arc:{status:e.arc?.status||t.arc.status},bimi:{status:e.bimi?.status||t.bimi.status,location:e.bimi?.location||null},receivedChain:e.receivedChain||[],headers:typeof e.headers=="string"?e.headers:""}}async function DY(e,t={}){let i=yY(),{ip:a,helo:n,sender:r,mta:s="spamscanner",timeout:o=1e4}=t;if(!a)return i.error="No client IP address given",i;try{let{authenticate:x}=await Tme(),A=t.resolver||_me(o,t.dnsServers),u=Xd.Buffer.isBuffer(e)?e:Xd.Buffer.from(String(e)),l=await x(u,{ip:a,helo:n||void 0,sender:r??void 0,mta:s,resolver:A});Object.assign(i,vb(l))}catch(x){Nme("authentication failed: %s",x.message),i.error=x.message}return i}var Lme={dkimPass:-.5,dkimFail:1,spfPass:-.5,spfFail:2,spfSoftfail:1,dmarcPass:-1.5,dmarcFail:3.5,arcPass:-.5,arcFail:1};function FY(e,t={}){let i={...Lme,...t},a=[],n=(s,o)=>{o!==0&&a.push({name:s,score:o})},r=[["DKIM",e?.dkim?.status?.result,{pass:i.dkimPass,fail:i.dkimFail}],["SPF",e?.spf?.status?.result,{pass:i.spfPass,fail:i.spfFail,softfail:i.spfSoftfail}],["DMARC",e?.dmarc?.status?.result,{pass:i.dmarcPass,fail:i.dmarcFail}],["ARC",e?.arc?.status?.result,{pass:i.arcPass,fail:i.arcFail}]];for(let[s,o,x]of r)o&&Object.hasOwn(x,o)&&n(`${s}_${o.toUpperCase()}`,x[o]);return{score:a.reduce((s,o)=>s+o.score,0),tests:a}}function wY(e,t){if(typeof e?.headers=="string"){let r=e.headers.match(/^authentication-results:[ \t]*([\s\S]*?)(?=\r?\n\S|(?![\s\S]))/im);if(r){let s=r[1].replaceAll(/\r?\n/g,`\r
456
456
  `).trim();return t?s.replace(/^[^;\s]+/,t):s}}let i=[t||"spamscanner"],a=e?.dkim?.status?.result||"none",n=e?.dkim?.results?.find(r=>r?.status?.result===a)?.signingDomain;return i.push(`dkim=${a}${n?` header.d=${n}`:""}`,`spf=${e?.spf?.status?.result||"none"}${e?.spf?.domain?` smtp.mailfrom=${e.spf.domain}`:""}`,`dmarc=${e?.dmarc?.status?.result||"none"}${e?.dmarc?.domain?` header.from=${e.dmarc.domain}`:""}`,`arc=${e?.arc?.status?.result||"none"}`),i.join(`;\r
457
- `)}function kY(e){return["spf","dkim","dmarc","arc"].map(t=>`${t}=${e?.[t]?.status?.result||"none"}`).join(" ")}var vY={name:"spamscanner",description:"Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",version:"7.0.2",author:"Forward Email (https://forwardemail.net)",bin:{spamscanner:"./dist/esm/cli.js"},bugs:{url:"https://github.com/spamscanner/spamscanner/issues"},c8:{all:!0,include:["src/**/*.js"],reporter:["text","lcov","json-summary"],"check-coverage":!0,branches:100,functions:100,lines:100,statements:100},contributors:["Forward Email (https://forwardemail.net)"],dependencies:{franc:"^6.2.0",mailauth:"^4.13.3",mailparser:"^3.9.16",tldts:"^7.0.17"},devDependencies:{"@commitlint/cli":"^19.8.1","@commitlint/config-conventional":"^19.8.1",c8:"^10.1.3",esbuild:"^0.28.2",fixpack:"^4.0.0",husky:"^9.1.7",hyparquet:"^1.31.2","lint-staged":"^15.5.2",marked:"^17.0.6","remark-cli":"^11.0.0","remark-lint-no-heading-punctuation":"^2.0.1","remark-preset-github":"^4.0.4","remark-retext":"^4.0.0",typescript:"^5.9.3",xo:"^1.2.3"},engines:{node:">=18"},exports:{".":{types:"./dist/types/index.d.ts",import:"./dist/esm/index.js",require:"./dist/cjs/index.cjs"},"./arf":{types:"./dist/types/arf.d.ts",import:"./dist/esm/arf.js",require:"./dist/cjs/arf.cjs"},"./package.json":"./package.json"},files:["dist/","model/"],homepage:"https://spamscanner.net",keywords:["anti-spam","antispam","arf","bayes","bayesian","clamav","classifier","dkim","dmarc","email","email-security","filter","llm","mail","milter","multilingual","ollama","phishing","postfix","scam","sendmail","spam","spam-filter","spamassassin","spamassassin-alternative","spf","virus"],license:"BUSL-1.1",main:"./dist/cjs/index.cjs",module:"./dist/esm/index.js",optionalDependencies:{re2:"^1.22.1"},packageManager:"pnpm@10.34.6",repository:{type:"git",url:"git+https://github.com/spamscanner/spamscanner.git"},scripts:{build:"node build.mjs && tsc --project tsconfig.json","i18n:check":"node scripts/i18n-check.js",lint:"fixpack && remark . -qfo && xo --fix","model:train":"node scripts/train-model.js","pre-commit":"lint-staged",prepare:"husky",prepublishOnly:"npm run build",pretest:"npm run lint && npm run build","site:build":"node scripts/build-site.js","site:check":"node scripts/check-links.js","site:serve":"node scripts/serve.js",test:"c8 node --test test/*.test.js","test:e2e":"node --test test/e2e/*.test.js"},type:"module",types:"./dist/types/index.d.ts"};var Q0=vY.version;var ya={ABORT:"A",BODY:"B",CONNECT:"C",MACRO:"D",BODYEOB:"E",HELO:"H",QUIT_NC:"K",HEADER:"L",MAIL:"M",EOH:"N",OPTNEG:"O",QUIT:"Q",RCPT:"R",DATA:"T",UNKNOWN:"U"},p0={ACCEPT:"a",CONTINUE:"c",DISCARD:"d",ADDHEADER:"h",INSHEADER:"i",CHGHEADER:"m",QUARANTINE:"q",REJECT:"r",TEMPFAIL:"t",REPLYCODE:"y",OPTNEG:"O"},Ib={ADDHDRS:1,CHGBODY:2,ADDRCPT:4,DELRCPT:8,CHGHDRS:16,QUARANTINE:32,CHGFROM:64},Qb={NOCONNECT:1,NOHELO:2,NOMAIL:4,NORCPT:8,NOBODY:16,NOHDRS:32,NOEOH:64,NR_HDR:128,NOUNKNOWN:256,NODATA:512,SKIP:1024,RCPT_REJ:2048,NR_CONN:4096,NR_HELO:8192,NR_MAIL:16384,NR_RCPT:32768,NR_DATA:65536,NR_UNKN:131072,NR_EOH:262144,NR_BODY:524288,HDR_LEADSPC:1048576},Ome=64*1024*1024;function jme(e,t=a0.Buffer.alloc(0)){let i=a0.Buffer.isBuffer(t)?t:a0.Buffer.from(t,"latin1"),a=a0.Buffer.alloc(5);return a.writeUInt32BE(i.length+1),a.write(e,4,"latin1"),a0.Buffer.concat([a,i])}function pl(e){let t=e.toString("latin1").split("\0");return t.at(-1)===""&&t.pop(),t}function Ws(e){return a0.Buffer.from(`${e}\0`,"latin1")}function IY(e){let t=String(e??"").trim(),i=/^<?([^<>]*)>?$/.exec(t);return i?i[1]:t}var Sb=class{constructor(t,i){this.socket=t,this.server=i,this.buffer=a0.Buffer.alloc(0),this.leadingSpace=!1,this.pending=[],this.processing=!1,this.resetConnection(),t.on("data",a=>this.receive(a)),t.on("error",a=>i.report(a))}resetConnection(){this.connection={},this.macros={},this.resetMessage()}resetMessage(){this.envelope={mailFrom:null,rcptTo:[]},this.headers=[],this.body=[],this.size=0,this.truncated=!1}send(t,i){this.socket.destroyed||this.socket.write(jme(t,i))}receive(t){for(this.buffer=a0.Buffer.concat([this.buffer,t]);this.buffer.length>=4;){let i=this.buffer.readUInt32BE(0);if(i===0||i>Ome){this.socket.destroy(new Error(`Invalid milter packet length ${i}`));return}if(this.buffer.length<4+i)return;this.pending.push({command:String.fromCodePoint(this.buffer[4]),data:this.buffer.subarray(5,4+i)}),this.buffer=this.buffer.subarray(4+i)}this.processing||this.process()}async process(){for(this.processing=!0;this.pending.length>0;){let{command:t,data:i}=this.pending.shift();try{await this.handle(t,i)}catch(a){this.server.report(a),this.send(this.server.options.onError==="accept"?p0.ACCEPT:p0.TEMPFAIL),this.resetMessage()}}this.processing=!1}async handle(t,i){let{options:a}=this.server;switch(t){case ya.OPTNEG:{let n=i.readUInt32BE(0),r=i.readUInt32BE(4),s=i.readUInt32BE(8),o=Ib.ADDHDRS|Ib.CHGHDRS|Ib.QUARANTINE;this.leadingSpace=!!(s&Qb.HDR_LEADSPC);let x=a0.Buffer.alloc(12);x.writeUInt32BE(Math.min(n,6),0),x.writeUInt32BE(r&o,4),x.writeUInt32BE(s&(Qb.HDR_LEADSPC|Qb.NOUNKNOWN),8),this.send(p0.OPTNEG,x);break}case ya.MACRO:{let n=pl(i.subarray(1));for(let r=0;r+1<n.length;r+=2)this.macros[n[r].replaceAll(/^{|}$/g,"")]=n[r+1];break}case ya.CONNECT:{let n=i.indexOf(0),r=i.subarray(0,n).toString("latin1"),s=String.fromCodePoint(i[n+1]),o=null;(s==="4"||s==="6")&&(o=pl(i.subarray(n+4))[0]),this.connection={hostname:r,family:s,remoteAddress:o&&o.replace(/^ipv6:/i,"")},this.send(p0.CONTINUE);break}case ya.HELO:{this.connection.helo=pl(i)[0],this.send(p0.CONTINUE);break}case ya.MAIL:{this.resetMessage(),this.envelope.mailFrom={address:IY(pl(i)[0])},this.send(p0.CONTINUE);break}case ya.RCPT:{this.envelope.rcptTo.push({address:IY(pl(i)[0])}),this.send(p0.CONTINUE);break}case ya.HEADER:{let[n,r=""]=pl(i);this.headers.push([n,r]),this.send(p0.CONTINUE);break}case ya.BODY:{this.size+i.length<=a.maxSize?(this.body.push(a0.Buffer.from(i)),this.size+=i.length):this.truncated=!0,this.send(p0.CONTINUE);break}case ya.BODYEOB:{await this.endOfMessage();break}case ya.ABORT:{this.resetMessage();break}case ya.QUIT_NC:{this.resetConnection();break}case ya.QUIT:{this.socket.end();break}default:this.send(p0.CONTINUE)}}rawMessage(){let t=this.headers.map(([i,a])=>`${i}:${this.leadingSpace||a.startsWith(" ")||a.startsWith(" ")?"":" "}${a}`).join(`\r
457
+ `)}function kY(e){return["spf","dkim","dmarc","arc"].map(t=>`${t}=${e?.[t]?.status?.result||"none"}`).join(" ")}var vY={name:"spamscanner",description:"Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",version:"7.0.3",author:"Forward Email (https://forwardemail.net)",bin:{spamscanner:"./dist/esm/cli.js"},bugs:{url:"https://github.com/spamscanner/spamscanner/issues"},c8:{all:!0,include:["src/**/*.js"],reporter:["text","lcov","json-summary"],"check-coverage":!0,branches:100,functions:100,lines:100,statements:100},contributors:["Forward Email (https://forwardemail.net)"],dependencies:{franc:"^6.2.0",mailauth:"^4.13.3",mailparser:"^3.9.16",tldts:"^7.0.17"},devDependencies:{"@commitlint/cli":"^19.8.1","@commitlint/config-conventional":"^19.8.1",c8:"^10.1.3",esbuild:"^0.28.2",fixpack:"^4.0.0",husky:"^9.1.7",hyparquet:"^1.31.2","lint-staged":"^15.5.2",marked:"^17.0.6","remark-cli":"^11.0.0","remark-lint-no-heading-punctuation":"^2.0.1","remark-preset-github":"^4.0.4","remark-retext":"^4.0.0",typescript:"^5.9.3",xo:"^1.2.3"},engines:{node:">=18"},exports:{".":{types:"./dist/types/index.d.ts",import:"./dist/esm/index.js",require:"./dist/cjs/index.cjs"},"./arf":{types:"./dist/types/arf.d.ts",import:"./dist/esm/arf.js",require:"./dist/cjs/arf.cjs"},"./package.json":"./package.json"},files:["dist/","model/"],homepage:"https://spamscanner.net",keywords:["anti-spam","antispam","arf","bayes","bayesian","clamav","classifier","dkim","dmarc","email","email-security","filter","llm","mail","milter","multilingual","ollama","phishing","postfix","scam","sendmail","spam","spam-filter","spamassassin","spamassassin-alternative","spf","virus"],license:"BUSL-1.1",main:"./dist/cjs/index.cjs",module:"./dist/esm/index.js",optionalDependencies:{re2:"^1.22.1"},packageManager:"pnpm@10.34.6",repository:{type:"git",url:"git+https://github.com/spamscanner/spamscanner.git"},scripts:{build:"node build.mjs && tsc --project tsconfig.json","i18n:check":"node scripts/i18n-check.js",lint:"fixpack && remark . -qfo && xo --fix","model:train":"node scripts/train-model.js","pre-commit":"lint-staged",prepare:"husky",prepublishOnly:"npm run build",pretest:"npm run lint && npm run build","site:build":"node scripts/build-site.js","site:check":"node scripts/check-links.js","site:serve":"node scripts/serve.js",test:"c8 node --test test/*.test.js","test:e2e":"node --test test/e2e/*.test.js"},type:"module",types:"./dist/types/index.d.ts"};var Q0=vY.version;var ya={ABORT:"A",BODY:"B",CONNECT:"C",MACRO:"D",BODYEOB:"E",HELO:"H",QUIT_NC:"K",HEADER:"L",MAIL:"M",EOH:"N",OPTNEG:"O",QUIT:"Q",RCPT:"R",DATA:"T",UNKNOWN:"U"},p0={ACCEPT:"a",CONTINUE:"c",DISCARD:"d",ADDHEADER:"h",INSHEADER:"i",CHGHEADER:"m",QUARANTINE:"q",REJECT:"r",TEMPFAIL:"t",REPLYCODE:"y",OPTNEG:"O"},Ib={ADDHDRS:1,CHGBODY:2,ADDRCPT:4,DELRCPT:8,CHGHDRS:16,QUARANTINE:32,CHGFROM:64},Qb={NOCONNECT:1,NOHELO:2,NOMAIL:4,NORCPT:8,NOBODY:16,NOHDRS:32,NOEOH:64,NR_HDR:128,NOUNKNOWN:256,NODATA:512,SKIP:1024,RCPT_REJ:2048,NR_CONN:4096,NR_HELO:8192,NR_MAIL:16384,NR_RCPT:32768,NR_DATA:65536,NR_UNKN:131072,NR_EOH:262144,NR_BODY:524288,HDR_LEADSPC:1048576},Ome=64*1024*1024;function jme(e,t=a0.Buffer.alloc(0)){let i=a0.Buffer.isBuffer(t)?t:a0.Buffer.from(t,"latin1"),a=a0.Buffer.alloc(5);return a.writeUInt32BE(i.length+1),a.write(e,4,"latin1"),a0.Buffer.concat([a,i])}function pl(e){let t=e.toString("latin1").split("\0");return t.at(-1)===""&&t.pop(),t}function Ws(e){return a0.Buffer.from(`${e}\0`,"latin1")}function IY(e){let t=String(e??"").trim(),i=/^<?([^<>]*)>?$/.exec(t);return i?i[1]:t}var Sb=class{constructor(t,i){this.socket=t,this.server=i,this.buffer=a0.Buffer.alloc(0),this.leadingSpace=!1,this.pending=[],this.processing=!1,this.resetConnection(),t.on("data",a=>this.receive(a)),t.on("error",a=>i.report(a))}resetConnection(){this.connection={},this.macros={},this.resetMessage()}resetMessage(){this.envelope={mailFrom:null,rcptTo:[]},this.headers=[],this.body=[],this.size=0,this.truncated=!1}send(t,i){this.socket.destroyed||this.socket.write(jme(t,i))}receive(t){for(this.buffer=a0.Buffer.concat([this.buffer,t]);this.buffer.length>=4;){let i=this.buffer.readUInt32BE(0);if(i===0||i>Ome){this.socket.destroy(new Error(`Invalid milter packet length ${i}`));return}if(this.buffer.length<4+i)return;this.pending.push({command:String.fromCodePoint(this.buffer[4]),data:this.buffer.subarray(5,4+i)}),this.buffer=this.buffer.subarray(4+i)}this.processing||this.process()}async process(){for(this.processing=!0;this.pending.length>0;){let{command:t,data:i}=this.pending.shift();try{await this.handle(t,i)}catch(a){this.server.report(a),this.send(this.server.options.onError==="accept"?p0.ACCEPT:p0.TEMPFAIL),this.resetMessage()}}this.processing=!1}async handle(t,i){let{options:a}=this.server;switch(t){case ya.OPTNEG:{let n=i.readUInt32BE(0),r=i.readUInt32BE(4),s=i.readUInt32BE(8),o=Ib.ADDHDRS|Ib.CHGHDRS|Ib.QUARANTINE;this.leadingSpace=!!(s&Qb.HDR_LEADSPC);let x=a0.Buffer.alloc(12);x.writeUInt32BE(Math.min(n,6),0),x.writeUInt32BE(r&o,4),x.writeUInt32BE(s&(Qb.HDR_LEADSPC|Qb.NOUNKNOWN),8),this.send(p0.OPTNEG,x);break}case ya.MACRO:{let n=pl(i.subarray(1));for(let r=0;r+1<n.length;r+=2)this.macros[n[r].replaceAll(/^{|}$/g,"")]=n[r+1];break}case ya.CONNECT:{let n=i.indexOf(0),r=i.subarray(0,n).toString("latin1"),s=String.fromCodePoint(i[n+1]),o=null;(s==="4"||s==="6")&&(o=pl(i.subarray(n+4))[0]),this.connection={hostname:r,family:s,remoteAddress:o&&o.replace(/^ipv6:/i,"")},this.send(p0.CONTINUE);break}case ya.HELO:{this.connection.helo=pl(i)[0],this.send(p0.CONTINUE);break}case ya.MAIL:{this.resetMessage(),this.envelope.mailFrom={address:IY(pl(i)[0])},this.send(p0.CONTINUE);break}case ya.RCPT:{this.envelope.rcptTo.push({address:IY(pl(i)[0])}),this.send(p0.CONTINUE);break}case ya.HEADER:{let[n,r=""]=pl(i);this.headers.push([n,r]),this.send(p0.CONTINUE);break}case ya.BODY:{this.size+i.length<=a.maxSize?(this.body.push(a0.Buffer.from(i)),this.size+=i.length):this.truncated=!0,this.send(p0.CONTINUE);break}case ya.BODYEOB:{await this.endOfMessage();break}case ya.ABORT:{this.resetMessage();break}case ya.QUIT_NC:{this.resetConnection();break}case ya.QUIT:{this.socket.end();break}default:this.send(p0.CONTINUE)}}rawMessage(){let t=this.headers.map(([i,a])=>`${i}:${this.leadingSpace||a.startsWith(" ")||a.startsWith(" ")?"":" "}${a}`).join(`\r
458
458
  `);return a0.Buffer.concat([a0.Buffer.from(this.headers.length>0?`${t}\r
459
459
  \r
460
460
  `:`\r
@@ -478,12 +478,12 @@ Classify the email between the markers. ${r?"Answer with one word: ham, spam, ph
478
478
  `)),t=[];continue}t!==null&&t.push(/^>+From /.test(i)?i.slice(1):i)}t!==null&&t.length>0&&(yield t.join(`
479
479
  `))}function cke(e){let t=[],i="",a=!1;for(let n=0;n<e.length;n++){let r=e[n];a?r==='"'&&e[n+1]==='"'?(i+='"',n++):r==='"'?a=!1:i+=r:r==='"'&&i===""?a=!0:r===","?(t.push(i),i=""):i+=r}return a?null:(t.push(i),t)}var Eke=new Set(["1","spam","junk","phishing","phish","scam","phishing email","true","yes"]),dke=new Set(["0","ham","legit","legitimate","not spam","not_spam","safe email","normal","false","no"]);function ZK(e){let t=String(e??"").trim().toLowerCase();return Eke.has(t)?"spam":dke.has(t)?"ham":null}async function*XK(e,t={}){let i=null,a="",n;for await(let r of HF(e)){let s=a?`${a}
480
480
  ${r}`:r,o=cke(s);if(o===null){a=s;continue}if(a="",i===null){i=o.map(u=>u.trim().toLowerCase()),n=pke(i,t,e);continue}let x=ZK(o[n.label]),A=o[n.text];x&&typeof A=="string"&&A.trim()!==""&&(yield{text:A,subject:n.subject===-1?"":o[n.subject]||"",label:x})}}function pke(e,t,i){let a=(r,s)=>{let o=r?[r.toLowerCase()]:s;return e.findIndex(x=>o.includes(x))},n={text:a(t.textColumn,["text","message","body","email text","email","content","sms","v2"]),label:a(t.labelColumn,["label","category","is_spam","spam","email type","class","type","label_text","v1","labels"]),subject:a(t.subjectColumn,["subject"])};if(n.text===-1||n.label===-1)throw new Error(`${i}: could not find the text and label columns (header: ${e.join(", ")}). Name them with textColumn and labelColumn.`);return n}function qF(e,t,i){let a=(t?[t]:i).find(n=>e[n]!==void 0&&e[n]!==null);return a===void 0?void 0:e[a]}async function*eZ(e,t={}){for await(let i of HF(e)){if(i.trim()==="")continue;let a;try{a=JSON.parse(i)}catch{continue}let n=qF(a,t.textColumn,["text","message","body","content"]),r=ZK(qF(a,t.labelColumn,["label_text","label","is_spam","category","labels"]));r&&typeof n=="string"&&n.trim()!==""&&(yield{text:n,subject:String(qF(a,t.subjectColumn,["subject"])??""),label:r})}}async function*tZ(e){let t=await S8.default.readdir(e,{withFileTypes:!0});t.sort((i,a)=>Q8.Buffer.compare(Q8.Buffer.from(i.name),Q8.Buffer.from(a.name)));for(let i of t){if(uke.has(i.name)||i.name.startsWith("."))continue;let a=WK.default.join(e,i.name);i.isDirectory()?i.name!=="tmp"&&(yield*tZ(a)):i.isFile()&&(yield a)}}function Bke(e){return/^From \S+/.test(e)}async function*iZ(e){let i=(await S8.default.stat(e)).isDirectory()?tZ(e):[e];for await(let a of i){let n=await S8.default.open(a),r;try{let{buffer:s,bytesRead:o}=await n.read(Q8.Buffer.alloc(64),0,64,0);r=s.subarray(0,o).toString("latin1")}finally{await n.close()}a.endsWith(".mbox")||a.endsWith(".mbox.gz")||Bke(r)?yield*lke(a):a.endsWith(".gz")||(yield await S8.default.readFile(a))}}async function hke(e,t={}){let i=await(0,rZ.simpleParser)(e,{skipImageLinks:!0,skipTextToHtml:!0,skipTextLinks:!0});return pA(i,t).features}function fke(e,t={}){return pA({text:e.text,subject:e.subject||""},t).features}async function*o5(e,t={}){let{onProgress:i,limit:a=1/0}=t,n=new Set,r=0,s=(o,x,A)=>{let u=(0,aZ.createHash)("sha1").update(A).digest("hex");return n.has(u)?null:(n.add(u),r++,i&&r%1e3===0&&i(r),{label:o,features:x,id:u})};for(let o of["spam","ham"])for(let x of e[o]||[]){let A=0;for await(let u of iZ(x)){if(A++>=a)break;let l=s(o,await hke(u),u);l&&(yield l)}}for(let o of e.datasets||[]){let x=typeof o=="string"?{file:o}:o,u=nZ.default.extname(x.file.replace(/\.gz$/,"")).toLowerCase()===".csv"?XK(x.file,x):eZ(x.file,x),l=0;for await(let E of u){if(l++>=a)break;let c=s(E.label,fke(E),`${E.subject}
481
- ${E.text}`);c&&(yield c)}}}async function zF(e,t={}){let i=t.classifier||new A0,a=0,n=0;for await(let r of o5(e,t))t.include&&!t.include(r)||(i.learn(r.features,r.label),r.label==="spam"?a++:n++);return{classifier:i,spam:a,ham:n}}async function PF(e,t){let i={truePositive:0,falsePositive:0,trueNegative:0,falseNegative:0,unsureSpam:0,unsureHam:0};for await(let{label:x,features:A}of t){let{category:u}=e.classify(A);u==="unsure"?i[x==="spam"?"unsureSpam":"unsureHam"]++:x==="spam"?i[u==="spam"?"truePositive":"falseNegative"]++:i[u==="ham"?"trueNegative":"falsePositive"]++}let a=i.truePositive+i.falseNegative+i.unsureSpam,n=i.trueNegative+i.falsePositive+i.unsureHam,r=(x,A)=>A===0?0:x/A,s=r(i.truePositive,i.truePositive+i.falsePositive),o=r(i.truePositive,a);return{...i,spam:a,ham:n,precision:s,recall:o,f1:r(2*s*o,s+o),falsePositiveRate:r(i.falsePositive,n),falseNegativeRate:r(i.falseNegative,a),unsureRate:r(i.unsureSpam+i.unsureHam,a+n),accuracy:r(i.truePositive+i.trueNegative,a+n)}}var rA=require("node:buffer"),IZ=require("node:fs/promises"),QZ=require("node:util"),SZ=bt(jF(),1);var T8=require("node:buffer");var x5=new Set(["ade","adp","apk","app","appimage","application","appref-ms","appx","appxbundle","asp","aspx","asx","bas","bat","bgi","cab","cer","chm","cmd","cnt","com","command","cpl","csh","der","diagcab","dll","dmg","docm","dotm","elf","exe","fxp","gadget","grp","hlp","hpj","hta","htc","img","inf","ins","ipa","iso","isp","its","jar","jnlp","js","jse","ksh","lnk","mad","maf","mag","mam","maq","mar","mas","mat","mau","mav","maw","mcf","mda","mde","mdt","mdw","mdz","msc","msh","msh1","msh1xml","msh2","msh2xml","mshxml","msi","msix","msixbundle","msp","mst","msu","nsh","ops","osd","pcd","pif","pkg","pl","plg","potm","ppam","ppsm","pptm","prf","prg","ps1","ps1xml","ps2","ps2xml","psc1","psc2","pst","py","pyc","pyo","pyw","pyz","pyzw","reg","scf","scpt","scr","sct","settingcontent-ms","sh","shb","shs","slk","sys","theme","tmp","url","vb","vbe","vbp","vbs","vhd","vhdx","vsmacros","vsw","webpnp","website","ws","wsb","wsc","wsf","wsh","xbap","xlam","xll","xlsm","xltm","xnk"]),N8=new Set(["docm","dotm","xlsm","xltm","xlam","pptm","potm","ppsm","ppam","sldm"]),gke=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","txt","rtf","jpg","jpeg","png","gif","zip","csv","odt","ods","html","htm","mp3","mp4","mov","wav","eml","msg","xml","json"]),mke=Yl([[8234,8238],[8294,8297]],"u"),Cke=new Set(["zip","rar","7z","gz","bz2","xz","cab","iso","tar","zst","lzh","ace","arj"]);function VF(e){if(!T8.Buffer.isBuffer(e)||e.length<4)return null;let t=(s,o)=>e.length>=s+o.length&&o.every((x,A)=>e[s+A]===x),i=(s,o)=>t(s,[...T8.Buffer.from(o,"latin1")]),a=(s,o=!1)=>({type:s,executable:o,archive:Cke.has(s)});if(i(0,"MZ"))return a("exe",!0);if(t(0,[127,69,76,70]))return a("elf",!0);let n=e.readUInt32BE(0);if([4277009102,4277009103,3472551422,3489328638].includes(n)||n===3405691582)return a("macho",!0);if(t(0,[76,0,0,0,1,20,2,0]))return a("lnk",!0);if(t(0,[208,207,17,224,161,177,26,225]))return a("ole");if(t(0,[80,75,3,4])||t(0,[80,75,5,6])){let s=GF(e).map(o=>o.name);return s.includes("AndroidManifest.xml")||s.includes("classes.dex")?a("apk",!0):s.includes("META-INF/MANIFEST.MF")&&s.some(o=>o.endsWith(".class"))?a("jar",!0):s.some(o=>o.startsWith("word/"))?a("docx"):s.some(o=>o.startsWith("xl/"))?a("xlsx"):s.some(o=>o.startsWith("ppt/"))?a("pptx"):a("zip")}if(i(0,"Rar!"))return a("rar");if(t(0,[55,122,188,175,39,28]))return a("7z");if(t(0,[31,139]))return a("gz");if(i(0,"MSCF"))return a("cab");if(i(32769,"CD001")||i(34817,"CD001")||i(36865,"CD001"))return a("iso");if(i(0,"conectix")||i(0,"vhdxfile"))return a("vhd",!0);if(i(0,"%PDF"))return a("pdf");if(i(0,String.raw`{\rt`))return a("rtf");if(t(0,[137,80,78,71]))return a("png");if(t(0,[255,216,255]))return a("jpg");if(i(0,"GIF8"))return a("gif");if(i(0,"RIFF")&&i(8,"WEBP"))return a("webp");if(i(0,"#!"))return a("script",!0);let r=e.subarray(0,1024).toString("utf8").replace(/^\uFEFF/u,"").trimStart().toLowerCase();return r.startsWith("<svg")||r.startsWith("<?xml")&&r.includes("<svg")?a("svg"):r.startsWith("<!doctype html")||r.startsWith("<html")||r.startsWith("<head")||r.startsWith("<script")?a("html"):null}var sZ=new Set(["zip","docx","xlsx","pptx"]),bke=new Set(["docx","xlsx","pptx"]),yke=new Set([...N8,"cer","der"]),oZ=new WeakMap;function GF(e){let t=oZ.get(e);return t||(t=Dke(e),oZ.set(e,t)),t}function Dke(e,t=2e3){let i=Math.max(0,e.length-65557),a=[];for(let s=e.length-22;s>=i;s--)e.readUInt32LE(s)===101010256&&(a.length===0||s+22+e.readUInt16LE(s+20)===e.length)&&a.push(s);let n=[],r=new Set;for(let s of a){let o=wke(e,s);o!==-1&&!r.has(o)&&(r.add(o),kke(e,o,t-n.length,n))}return n}var Fke=33639248,YF=(e,t)=>t>=0&&t+46<=e.length&&e.readUInt32LE(t)===Fke;function wke(e,t){let i=e.readUInt32LE(t+16),a=e.readUInt32LE(t+12),n=t-20;if((i===4294967295||a===4294967295)&&n>=0&&e.readUInt32LE(n)===117853008){let r=Number(e.readBigUInt64LE(n+8)),s=x=>x>=0&&x+56<=n&&e.readUInt32LE(x)===101075792,o=s(r)?r:n-56;s(o)&&(a=Number(e.readBigUInt64LE(o+40)),i=Number(e.readBigUInt64LE(o+48)))}if(YF(e,i))return i;for(let r of[t,n-56]){let s=r-a;if(s<r&&YF(e,s))return s}return-1}function kke(e,t,i,a){for(let n=0;n<i&&YF(e,t);n++){let r=e.readUInt16LE(t+8),s=e.readUInt32LE(t+24),o=e.readUInt16LE(t+28),x=e.readUInt16LE(t+30),A=e.readUInt16LE(t+32),u=e.subarray(t+46,t+46+o).toString(r&2048?"utf8":"latin1");a.push({name:u,encrypted:(r&1)===1,size:s}),t+=46+o+x+A}}function Ml(e){let t=as(String(e).toLowerCase().split(/[/\\]/).pop(),". "),i=t.lastIndexOf(".");return i===-1?"":t.slice(i+1).trim()}function vke(e){return["_VBA_PROJECT","VBA","Macros"].map(i=>T8.Buffer.from(i,"utf16le")).some(i=>e.includes(i))}var Ike=Ge(String.raw`\/(?:JavaScript|JS|Launch|EmbeddedFile)\b`);function xZ(e=[]){let t=[];for(let i of e){let a=typeof i.filename=="string"?i.filename:"",n=a||"unnamed attachment",r=T8.Buffer.isBuffer(i.content)?i.content:null,s=Ml(a),o=r?VF(r):null,x=(l,E,c={})=>t.push({type:l,filename:n,message:E,...c});mke.test(a)&&x("rtl_override",`Attachment "${n}" uses a right-to-left override to hide its real extension`);let A=as(a.toLowerCase(),". ").split(".");if(A.length>=3&&x5.has(s)&&gke.has(A.at(-2))&&x("double_extension",`Attachment "${n}" has a document extension followed by a program extension`,{extension:s}),x5.has(s)&&!N8.has(s)?x("executable",`Attachment "${n}" is a file type that runs code (.${s})`,{extension:s}):o?.executable&&x(s&&!x5.has(s)?"disguised_executable":"executable",`Attachment "${n}" contains a program (${o.type})${s?` but is named .${s}`:""}`,{extension:o.type}),!r){N8.has(s)&&x("macro",`Attachment "${n}" is an Office document type that holds macros (.${s})`,{extension:s});continue}if(sZ.has(o?.type)||s==="zip"&&!o?.executable&&GF(r).length>0){let l=GF(r),E=bke.has(o?.type);l.some(d=>/(?:^|\/)vbaproject\.bin$/i.test(d.name))?x("macro",`Attachment "${n}" is an Office document with a VBA macro project`,{extension:s}):N8.has(s)?x("macro",`Attachment "${n}" is an Office document type that holds macros (.${s})`,{extension:s}):E&&l.some(d=>N8.has(Ml(d.name)))&&x("macro",`Attachment "${n}" is an Office document holding another with macros`,{extension:s});let c=l.filter(d=>x5.has(Ml(d.name))&&!(E&&yke.has(Ml(d.name))));c.length>0&&x("executable_in_archive",`Archive "${n}" contains ${c.length===1?"a program":`${c.length} programs`} (${c.slice(0,3).map(d=>d.name.split("/").pop()).join(", ")})`,{extension:Ml(c[0].name),extensions:[...new Set(c.map(d=>Ml(d.name)))]}),(o?.type==="zip"||!sZ.has(o?.type))&&l.some(d=>d.encrypted)&&x("encrypted_archive",`Archive "${n}" is password protected, so its contents cannot be scanned`,{extension:s})}else o?.type==="ole"?vke(r)&&x("macro",`Attachment "${n}" is an Office document with a VBA macro project`,{extension:s}):o?.type==="pdf"||s==="pdf"?Ike.test(r.toString("latin1"))&&x("pdf_active",`PDF "${n}" contains JavaScript, a launch action or an embedded file`,{extension:"pdf"}):o?.type==="rtf"&&/\\object\b|\\objdata\b|\\objupdate\b/.test(r.toString("latin1"))&&x("rtf_object",`RTF document "${n}" embeds an OLE object`,{extension:"rtf"});if(o?.type==="html"||o?.type==="svg"||["htm","html","shtml","xhtml","svg"].includes(s)){let l=r.subarray(0,2e5).toString("utf8"),E=/<script\b|<form\b|\bon(?:load|error|click)\s*=|javascript:|window\.location|document\.location|atob\(/i.test(l);x("html_attachment",`Attachment "${n}" is ${o?.type==="svg"||s==="svg"?"an SVG image":"a web page"}${E?" with scripts or forms":""}`,{extension:s||o?.type,active:E})}}return t}var A5=require("node:buffer"),AZ=require("node:fs"),uZ=bt(require("node:net"),1),Qke=["/var/run/clamav/clamd.ctl","/run/clamav/clamd.ctl","/run/clamd.scan/clamd.sock","/var/run/clamd.scan/clamd.sock","/var/run/clamav/clamd.sock","/tmp/clamd.socket","/opt/homebrew/var/run/clamav/clamd.sock","/usr/local/var/run/clamav/clamd.sock"];function Ske(e={}){if(e.socket)return{path:e.socket};if(e.host||e.port)return{host:e.host||"127.0.0.1",port:Number(e.port)||3310};let t=(e.socketPaths||Qke).find(i=>(0,AZ.existsSync)(i));return t?{path:t}:null}function Nke(e,t,i){return new Promise((a,n)=>{let r=uZ.default.createConnection(e),s=[],o=(x,A)=>{r.destroy(),x?n(x):a(A)};r.setTimeout(i,()=>o(new Error(`clamd did not answer within ${i} ms`))),r.on("connect",()=>t(r)),r.on("data",x=>s.push(x)),r.on("end",()=>o(null,A5.Buffer.concat(s).toString("utf8").replaceAll("\0","").trim())),r.on("error",x=>o(x))})}async function lZ(e,t={}){let i=Ske(t);if(!i)throw new Error("No clamd socket found; set clamav.socket or clamav.host and clamav.port");let a=t.chunkSize??65536,n=await Nke(i,s=>{s.write("zINSTREAM\0");for(let o=0;o<e.length;o+=a){let x=e.subarray(o,o+a),A=A5.Buffer.alloc(4);A.writeUInt32BE(x.length),s.write(A),s.write(x)}s.end(A5.Buffer.alloc(4))},t.timeout??3e4);if(/\bOK$/.test(n))return{infected:!1,viruses:[],reply:n};let r=[...n.matchAll(/:\s*(.+?)\s+FOUND/g)].map(s=>s[1]);if(r.length>0)return{infected:!0,viruses:r,reply:n};throw new Error(`clamd: ${n||"no reply"}`)}var EZ=bt(require("node:dns"),1),u5=require("node:net");var Tke=["1.1.1.2","1.0.0.2"],Rke=["1.1.1.3","1.0.0.3"];function _ke(e,t){let i=(0,u5.isIP)(e);if(i===4)return`${e.split(".").reverse().join(".")}.${t}`;if(i===6){let[a,n=""]=e.split("::"),r=a?a.split(":"):[],s=n?n.split(":"):[];return`${[...[...r,...Array.from({length:8-r.length-s.length}).fill("0"),...s].map(A=>A.padStart(4,"0")).join("")].reverse().join(".")}.${t}`}return null}function cZ(e,t){return!(!/^127(?:\.\d+){3}$/.test(e)||e.startsWith("127.255.255.")||e==="127.0.0.255"||t.endsWith("uribl.com")&&e==="127.0.0.1")}var l5=class{constructor(t={}){this.timeout=t.timeout??3e3,this.servers=t.servers||null,this.cacheSize=t.cacheSize??1e4,this.cacheTtl=t.cacheTtl??6e5,this.cache=new Map,this.resolvers=new Map,this.customResolve4=t.resolve4||null}resolver(t){let i=t?t.join(","):"";if(!this.resolvers.has(i)){let a=new EZ.default.promises.Resolver({timeout:this.timeout,tries:1});t&&a.setServers(t),this.resolvers.set(i,a)}return this.resolvers.get(i)}async resolve4(t,i=this.servers){let a=`${i?i.join(","):""}|${t}`,n=this.cache.get(a);if(n&&n.expires>Date.now())return n.value;let r,s;try{let o=this.customResolve4?this.customResolve4(t,i):this.resolver(i).resolve4(t);r=await Promise.race([o,new Promise(x=>{s=setTimeout(()=>x(null),this.timeout)})])}catch{r=[]}finally{clearTimeout(s)}return r===null?[]:(this.cache.set(a,{value:r,expires:Date.now()+this.cacheTtl}),this.cache.size>this.cacheSize&&this.cache.delete(this.cache.keys().next().value),r)}async checkIp(t,i=[]){return(await Promise.all(i.map(async n=>{let r=_ke(t,n);if(!r)return null;let o=(await this.resolve4(r)).filter(x=>cZ(x,n));return o.length>0?{zone:n,value:t,answers:o}:null}))).filter(Boolean)}async checkDomains(t=[],i=[]){let a=[];for(let r of t)if(!(0,u5.isIP)(r))for(let s of i)a.push((async()=>{let x=(await this.resolve4(`${r}.${s}`)).filter(A=>cZ(A,s));return x.length>0?{zone:s,value:r,answers:x}:null})());return(await Promise.all(a)).filter(Boolean)}async checkCloudflare(t=[],i={}){let{adult:a=!0,malwareServers:n=Tke,familyServers:r=Rke}=i;return(await Promise.all(t.map(async o=>(0,u5.isIP)(o)?null:(await this.resolve4(o,n)).includes("0.0.0.0")?{host:o,category:"malware"}:(a?await this.resolve4(o,r):[]).includes("0.0.0.0")?{host:o,category:"adult"}:null))).filter(Boolean)}};var hZ=require("node:url"),fZ=bt(R5(),1);var Uke=["alipay","auspost","bancolombia","bnpparibas","bradesco","caixabank","canadapost","commerzbank","coupang","creditagricole","deutschebank","flipkart","gosuslugi","japanpost","kakaobank","klarna","kucoin","lazada","mercadolibre","mercadolivre","mercadopago","mercari","natwest","nubank","phonepe","postbank","rakuten","sberbank","swisscom","trustwallet","unicredit","vodafone","wechat","yandex"],Lke=["adobe","airbnb","alibaba","aliexpress","amazon","americanexpress","amex","apple","appleid","att","binance","bankofamerica","barclays","bestbuy","blockchain","booking","bybit","cashapp","chase","citibank","cloudflare","coinbase","comcast","costco","dhl","discord","docusign","dropbox","ebay","facebook","fedex","forwardemail","github","gitlab","gmail","godaddy","google","hmrc","hotmail","hsbc","icloud","instagram","intuit","irs","kraken","ledger","linkedin","mastercard","mcafee","metamask","microsoft","microsoftonline","namecheap","netflix","norton","office","office365","okta","onedrive","outlook","paypal","proton","protonmail","quickbooks","revolut","roblox","royalmail","salesforce","samsung","santander","sharepoint","shopify","skype","slack","sparkasse","spotify","steam","steampowered","stripe","telegram","tiktok","tmobile","trezor","tutanota","twitter","uber","ups","usps","venmo","verizon","visa","walmart","wellsfargo","whatsapp","wise","xfinity","yahoo","zelle","zoom",...Uke],gZ=["account","accounts","alert","alerts","auth","authentication","billing","confirm","login","logon","password","recovery","refund","secure","security","signin","signon","suspended","unlock","update","validate","verification","verify"],dZ=new Set(["apple","blockchain","booking","chase","discord","kraken","ledger","office","proton","slack","steam","stripe"]),Mke=new Set(["akuten","alzada","appel","applied","auslost","azada","coupant","gechat","klara","lanada","lanzada","lapada","latada","lavada","mazada","mercar","mercati","pazada","phoneme","postbak","raketen","rauten","razada","stirpe","vazada","venom","webchat"]),Oke=["adobelogin.com","amazon-adsystem.com","apple-cloudkit.com","apple-dns.net","bnpparibas-am.com","canadapost-postescanada.ca","cdn-apple.com","cloudflare-dns.com","credit-agricole.com","credit-agricole.fr","deutsche-bank.de","google-analytics.com","icloud-content.com","images-amazon.com","instagram-engineering.com","media-amazon.com","okta-emea.com","paypal-community.com","paypal-objects.com","paypal-prepaid.com","paypal-status.com","rakuten-bank.co.jp","rakuten-card.co.jp","rakuten-sec.co.jp","sberbank-ast.ru","slack-edge.com","slack-msgs.com","ssl-images-amazon.com","t-mobile.com","yahoo-corp.jp","yandex-team.ru"],pZ=/^(?:com|net|org|gov|edu)-/,jke=/^(?:co|info|biz|io|app|us|uk|de|fr|es|it|ca|au|jp|br|in)-/,qke=/^(?:co|com)-(?:uk|jp|au|nz|za|in|kr|br)\b/,Hke={\u0430:"a",\u04D3:"a",\u0432:"b",\u0441:"c","\u0501":"d",\u0435:"e",\u0451:"e",\u04BB:"h",\u0456:"i",\u0457:"i",\u0458:"j",\u043A:"k","\u04CF":"l",\u043C:"m",\u043E:"o",\u04E9:"o",\u0440:"p","\u051B":"q",\u0433:"r",\u0455:"s",\u0442:"t",\u0443:"y",\u04AF:"y",\u0445:"x","\u051D":"w",\u044C:"b",\u043F:"n",\u043D:"h",\u03B1:"a",\u03B2:"b",\u03F2:"c",\u03B5:"e",\u03B7:"n",\u03B9:"i",\u03AF:"i",\u03F3:"j",\u03BA:"k",\u03BC:"u",\u03BD:"v",\u03BF:"o",\u03CC:"o",\u03C1:"p",\u03C4:"t",\u03C5:"u",\u03C7:"x",\u03C9:"w",\u03B3:"y",\u0251:"a",\u0261:"g",\u0269:"i",\u0131:"i","\u0237":"j",\u029F:"l",\u0274:"n","\u1D0F":"o",\u0280:"r",\u0475:"v",\u028F:"y","\u1D22":"z","\u2170":"i","\u217C":"l","\u217D":"c","\u217E":"d","\u217F":"m",\u2113:"l","\u0660":"o","\u06F0":"o",\u00E0:"a",\u00E1:"a",\u00E2:"a",\u00E3:"a",\u00E4:"a",\u00E5:"a",\u0101:"a",\u0103:"a",\u0105:"a",\u00E7:"c",\u0107:"c",\u010D:"c",\u010F:"d",\u00E8:"e",\u00E9:"e",\u00EA:"e",\u00EB:"e",\u0113:"e",\u0119:"e",\u011B:"e",\u00EC:"i",\u00ED:"i",\u00EE:"i",\u00EF:"i",\u012B:"i",\u0142:"l",\u00F1:"n",\u0144:"n",\u00F2:"o",\u00F3:"o",\u00F4:"o",\u00F5:"o",\u00F6:"o",\u00F8:"o",\u014D:"o",\u0151:"o",\u0155:"r",\u0159:"r",\u015B:"s",\u0161:"s",\u015F:"s",\u0165:"t",\u0163:"t",\u00F9:"u",\u00FA:"u",\u00FB:"u",\u00FC:"u",\u016B:"u",\u016F:"u",\u0171:"u",\u00FD:"y",\u00FF:"y",\u017A:"z",\u017C:"z",\u017E:"z"},zke=[[/rn/g,"m"],[/vv/g,"w"],[/cl/g,"d"],[/0/g,"o"],[/1/g,"l"],[/3/g,"e"],[/4/g,"a"],[/5/g,"s"],[/7/g,"t"],[/8/g,"b"]];function JF(e){let t="";for(let i of String(e).normalize("NFKC").toLowerCase())t+=Hke[i]||i;return t}var Pke=256;function BZ(e){let t=new Set([e]);for(let[i,a]of zke)if(e.includes(i.source))for(let n of t){if(t.size>=Pke/2)break;t.add(n.replace(i,a))}for(let i of t)t.add(i.replaceAll("l","i"));return t.delete(e),t}function Gke(e,t,i=2){if(Math.abs(e.length-t.length)>i)return i+1;let a=null,n=Array.from({length:t.length+1},(r,s)=>s);for(let r=1;r<=e.length;r++){let s=[r],o=r;for(let x=1;x<=t.length;x++){let A=e[r-1]===t[x-1]?0:1;s[x]=Math.min(n[x]+1,s[x-1]+1,n[x-1]+A),a&&r>1&&x>1&&e[r-1]===t[x-2]&&e[r-2]===t[x-1]&&(s[x]=Math.min(s[x],a[x-2]+1)),o=Math.min(o,s[x])}if(o>i)return i+1;a=n,n=s}return n[t.length]}function Yke(e,t){for(let i of gZ){if(e.length<=i.length+4)continue;let a=e.endsWith(i)?e.slice(0,-i.length):null;if(a&&a.length>=5&&t.has(a)&&!dZ.has(a))return a;let n=e.startsWith(i)?e.slice(i.length):null;if(n&&n.length>=5&&t.has(n)&&!dZ.has(n))return n}return null}function Vke(e){let t=new Set;for(let i of e)new RegExp("\\p{L}","u").test(i)&&t.add(EA(i));return t}var Jke=[new Set(["Han","Hiragana","Katakana","Latin"]),new Set(["Han","Hangul","Latin"])];function Wke(e){return e.size>1&&!Jke.some(t=>[...e].every(i=>t.has(i)))}var R8=class{constructor(t={}){this.options=t,this.brands=new Set([...t.brands||Lke,...t.extraBrands||[]].map(i=>i.toLowerCase())),this.allowlist=new Set([...Oke,...t.allowlist||[]].map(i=>i.toLowerCase())),this.cache=new Map}detectHomographAttack(t){let i=typeof t=="string"?t.trim().toLowerCase().replace(/\.$/,""):"";if(this.cache.has(i))return this.cache.get(i);let a=this.analyse(i);return this.cache.set(i,a),this.cache.size>5e3&&this.cache.delete(this.cache.keys().next().value),a}analyse(t){let i=t&&(0,hZ.domainToUnicode)(t)||t,a={domain:t,unicode:i,isIDN:i!==t||/[^ -~]/.test(i),riskScore:0,riskFactors:[],brand:null,recommendations:[],confidence:0},n=(0,fZ.parse)(i),r=n.domainWithoutSuffix;if(!r||n.isIp||this.allowlist.has(n.domain))return a;let s=(c,d,B)=>{a.riskFactors.push(d),c>a.riskScore&&(a.riskScore=c,a.brand=B)};if(this.brands.has(r))return a;let o=Vke(r),x=JF(r);Wke(o)&&s(.6,`"${i}" mixes ${[...o].join(" and ")} letters`,null);let A=x===r?null:[x,x.replaceAll("-","")].find(c=>this.brands.has(c));if(A)s(.95,`"${i}" imitates ${A} with lookalike letters`,A);else if(/^[a-z\d-]+$/.test(x)){let c=BZ(x),d=[...c].find(f=>this.brands.has(f));if(d)s(.85,`"${i}" imitates ${d} by swapping characters`,d);else if(x.length>=5&&!Mke.has(x)){let f=[...this.brands].find(g=>g.length>=5&&Gke(x,g,1)===1);f&&s(.5,`"${i}" is one letter away from ${f}`,f)}let B=x.includes("-")?x.split("-"):[];for(let f of B){if(f.length<4)continue;if(this.brands.has(f)){s(.6,`"${i}" puts ${f} inside another domain name`,f);break}let g=[...BZ(f)].find(b=>this.brands.has(b));if(g){s(.85,`"${i}" imitates ${g} by swapping characters`,g);break}}for(let f of[x,...B,...c]){let g=Yke(f,this.brands);if(g){s(.6,`"${i}" joins ${g} to a phishing word`,g);break}}}let u=n.subdomain?n.subdomain.split("."):[],l=u.map(c=>JF(c)).find(c=>c.length>=4&&this.brands.has(c));l&&(this.options.strictMode||u.length>=2)&&s(.6,`"${i}" puts ${l} in front of another domain`,l);let E=u.length>0?JF(u.at(-1)):"";if(E.length>=3&&this.brands.has(E)&&(pZ.test(x)||jke.test(x))){let c=pZ.test(x)||qke.test(x)||gZ.some(d=>x.includes(d));s(c?.85:.6,`"${i}" starts with ${E}'s address and continues into another domain`,E)}return a.confidence=a.riskScore,a.riskScore>=.5&&a.recommendations.push(a.brand?`Do not sign in from this link; go to ${a.brand}'s site directly`:"Treat links to this domain as untrusted"),a}};var $ke="XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X",Kke=/cheecck y0ur acc0untt|recorded you|you've been hacked|account is hacked|personal data has leaked|private information has been stolen/i,Zke=/\b(?:ignore|disregard|forget)\s+(?:all\s+|any\s+)?(?:the\s+)?(?:previous|prior|above|earlier|preceding)\s+(?:instructions?|prompts?|rules)\b|\bclassify\s+(?:this|the)\s+(?:e-?mail|message)\s+as\s+(?:ham|safe|legitimate|not\s+spam|clean)\b|\b(?:system|developer)\s+prompt\s*:|\byou\s+are\s+(?:an?\s+)?(?:ai|llm|language model|spam filter|email classifier)\b.{0,80}\b(?:must|should)\b/is,Xke=/^paypal\.(?:[a-z]{2,3}|com?\.[a-z]{2})$/i,eve=new Set(["PPC001017","RT000238","RT000542","RT002947","RTI003384"]),tve=/\binvoice from\b|\bsent you an invoice\b|\b(?:money|payment) request\b|\brequest(?:ed)? (?:money|a payment|for (?:money|payment))\b|\brequested [$€£¥]|\brequested \d|\brechnung von\b|\bzahlungsaufforderung\b|\bgeldanforderung\b|\bfacture de\b|\bdemande d['’]argent\b|\bdemande de paiement\b|\bfactura de\b|\bsolicitud de (?:dinero|pago)\b|\bfattura da\b|\brichiesta di (?:denaro|pagamento)\b|\bfactuur van\b|\bbetaalverzoek\b|\bfatura de\b|\bpedido de (?:dinheiro|pagamento)\b/i,ive=/\bsfv:(?:spm|skb|sks)\b/i,ave=/\bcat:(?:ospm|spm|hspm|phsh|hphsh|hphish|malw|spoof)\b/i,nve=["paypal","apple","icloud","microsoft","outlook","office 365","amazon","netflix","docusign","dhl","fedex","ups","usps","coinbase","binance","metamask","wells fargo","chase","bank of america","american express","facebook","instagram","whatsapp","linkedin","dropbox","adobe","google","gmail","yahoo","norton","mcafee","geek squad","irs","hmrc","royal mail","la poste","sparkasse","santander","hsbc","barclays"];function mZ(e,t){let i=e.headers?.get?.(t);return i==null?"":typeof i=="string"?i:Array.isArray(i)?i.map(a=>typeof a=="string"?a:a?.text??a?.value??"").join(" "):i.text??i.value??String(i)}function CZ(e){let t=String(e),i=t.lastIndexOf("@");return i===-1?"":t.slice(i+1).toLowerCase()}function rve(e={},t={}){let i={...t},a=e.from?.value?.find(n=>n.address)?.address||"";return a&&!i.originalFromAddress&&(i.originalFromAddress=a.toLowerCase()),i.originalFromAddress&&(i.originalFromAddressDomain||=CZ(i.originalFromAddress),i.originalFromAddressRootDomain||=Gt(i.originalFromAddressDomain)),i.resolvedClientHostname&&(i.resolvedClientHostname=i.resolvedClientHostname.toLowerCase().replace(/\.$/,""),i.resolvedRootClientHostname||=Gt(i.resolvedClientHostname)),i}function bZ(e={},t={}){let{threshold:i=5,authentication:a=null,now:n=new Date}=t,r=rve(e,t.session),s=[],o=null,x=(B,f,g,b=null)=>{s.push({name:B,score:f,message:g}),o||=b},A=typeof e.subject=="string"?e.subject:"",u=typeof e.text=="string"?e.text:"",l=typeof e.html=="string"?e.html:"",E=Array.isArray(e.headerLines)?e.headerLines.map(B=>B.line).join(`
481
+ ${E.text}`);c&&(yield c)}}}async function zF(e,t={}){let i=t.classifier||new A0,a=0,n=0;for await(let r of o5(e,t))t.include&&!t.include(r)||(i.learn(r.features,r.label),r.label==="spam"?a++:n++);return{classifier:i,spam:a,ham:n}}async function PF(e,t){let i={truePositive:0,falsePositive:0,trueNegative:0,falseNegative:0,unsureSpam:0,unsureHam:0};for await(let{label:x,features:A}of t){let{category:u}=e.classify(A);u==="unsure"?i[x==="spam"?"unsureSpam":"unsureHam"]++:x==="spam"?i[u==="spam"?"truePositive":"falseNegative"]++:i[u==="ham"?"trueNegative":"falsePositive"]++}let a=i.truePositive+i.falseNegative+i.unsureSpam,n=i.trueNegative+i.falsePositive+i.unsureHam,r=(x,A)=>A===0?0:x/A,s=r(i.truePositive,i.truePositive+i.falsePositive),o=r(i.truePositive,a);return{...i,spam:a,ham:n,precision:s,recall:o,f1:r(2*s*o,s+o),falsePositiveRate:r(i.falsePositive,n),falseNegativeRate:r(i.falseNegative,a),unsureRate:r(i.unsureSpam+i.unsureHam,a+n),accuracy:r(i.truePositive+i.trueNegative,a+n)}}var rA=require("node:buffer"),IZ=require("node:fs/promises"),QZ=require("node:util"),SZ=bt(jF(),1);var T8=require("node:buffer");var x5=new Set(["ade","adp","apk","app","appimage","application","appref-ms","appx","appxbundle","asp","aspx","asx","bas","bat","bgi","cab","cer","chm","cmd","cnt","com","command","cpl","csh","der","diagcab","dll","dmg","docm","dotm","elf","exe","fxp","gadget","grp","hlp","hpj","hta","htc","img","inf","ins","ipa","iso","isp","its","jar","jnlp","js","jse","ksh","lnk","mad","maf","mag","mam","maq","mar","mas","mat","mau","mav","maw","mcf","mda","mde","mdt","mdw","mdz","msc","msh","msh1","msh1xml","msh2","msh2xml","mshxml","msi","msix","msixbundle","msp","mst","msu","nsh","ops","osd","pcd","pif","pkg","pl","plg","potm","ppam","ppsm","pptm","prf","prg","ps1","ps1xml","ps2","ps2xml","psc1","psc2","pst","py","pyc","pyo","pyw","pyz","pyzw","reg","scf","scpt","scr","sct","settingcontent-ms","sh","shb","shs","slk","sys","theme","tmp","url","vb","vbe","vbp","vbs","vhd","vhdx","vsmacros","vsw","webpnp","website","ws","wsb","wsc","wsf","wsh","xbap","xlam","xll","xlsm","xltm","xnk"]),N8=new Set(["docm","dotm","xlsm","xltm","xlam","pptm","potm","ppsm","ppam","sldm"]),gke=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","txt","rtf","jpg","jpeg","png","gif","zip","csv","odt","ods","html","htm","mp3","mp4","mov","wav","eml","msg","xml","json"]),mke=Yl([[8234,8238],[8294,8297]],"u"),Cke=new Set(["zip","rar","7z","gz","bz2","xz","cab","iso","tar","zst","lzh","ace","arj"]);function VF(e){if(!T8.Buffer.isBuffer(e)||e.length<4)return null;let t=(s,o)=>e.length>=s+o.length&&o.every((x,A)=>e[s+A]===x),i=(s,o)=>t(s,[...T8.Buffer.from(o,"latin1")]),a=(s,o=!1)=>({type:s,executable:o,archive:Cke.has(s)});if(i(0,"MZ")&&e.length>=64&&t(e.readUInt32LE(60),[80,69,0,0]))return a("exe",!0);if(t(0,[127,69,76,70]))return a("elf",!0);let n=e.readUInt32BE(0);if([4277009102,4277009103,3472551422,3489328638].includes(n)||n===3405691582)return a("macho",!0);if(t(0,[76,0,0,0,1,20,2,0]))return a("lnk",!0);if(t(0,[208,207,17,224,161,177,26,225]))return a("ole");if(t(0,[80,75,3,4])||t(0,[80,75,5,6])){let s=GF(e).map(o=>o.name);return s.includes("AndroidManifest.xml")||s.includes("classes.dex")?a("apk",!0):s.includes("META-INF/MANIFEST.MF")&&s.some(o=>o.endsWith(".class"))?a("jar",!0):s.some(o=>o.startsWith("word/"))?a("docx"):s.some(o=>o.startsWith("xl/"))?a("xlsx"):s.some(o=>o.startsWith("ppt/"))?a("pptx"):a("zip")}if(i(0,"Rar!"))return a("rar");if(t(0,[55,122,188,175,39,28]))return a("7z");if(t(0,[31,139]))return a("gz");if(i(0,"MSCF"))return a("cab");if(i(32769,"CD001")||i(34817,"CD001")||i(36865,"CD001"))return a("iso");if(i(0,"conectix")||i(0,"vhdxfile"))return a("vhd",!0);if(i(0,"%PDF"))return a("pdf");if(i(0,String.raw`{\rt`))return a("rtf");if(t(0,[137,80,78,71]))return a("png");if(t(0,[255,216,255]))return a("jpg");if(i(0,"GIF8"))return a("gif");if(i(0,"RIFF")&&i(8,"WEBP"))return a("webp");if(i(0,"#!"))return a("script",!0);let r=e.subarray(0,1024).toString("utf8").replace(/^\uFEFF/u,"").trimStart().toLowerCase();return r.startsWith("<svg")||r.startsWith("<?xml")&&r.includes("<svg")?a("svg"):r.startsWith("<!doctype html")||r.startsWith("<html")||r.startsWith("<head")||r.startsWith("<script")?a("html"):null}var sZ=new Set(["zip","docx","xlsx","pptx"]),bke=new Set(["docx","xlsx","pptx"]),yke=new Set([...N8,"cer","der"]),oZ=new WeakMap;function GF(e){let t=oZ.get(e);return t||(t=Dke(e),oZ.set(e,t)),t}function Dke(e,t=2e3){let i=Math.max(0,e.length-65557),a=[];for(let s=e.length-22;s>=i;s--)e.readUInt32LE(s)===101010256&&(a.length===0||s+22+e.readUInt16LE(s+20)===e.length)&&a.push(s);let n=[],r=new Set;for(let s of a){let o=wke(e,s);o!==-1&&!r.has(o)&&(r.add(o),kke(e,o,t-n.length,n))}return n}var Fke=33639248,YF=(e,t)=>t>=0&&t+46<=e.length&&e.readUInt32LE(t)===Fke;function wke(e,t){let i=e.readUInt32LE(t+16),a=e.readUInt32LE(t+12),n=t-20;if((i===4294967295||a===4294967295)&&n>=0&&e.readUInt32LE(n)===117853008){let r=Number(e.readBigUInt64LE(n+8)),s=x=>x>=0&&x+56<=n&&e.readUInt32LE(x)===101075792,o=s(r)?r:n-56;s(o)&&(a=Number(e.readBigUInt64LE(o+40)),i=Number(e.readBigUInt64LE(o+48)))}if(YF(e,i))return i;for(let r of[t,n-56]){let s=r-a;if(s<r&&YF(e,s))return s}return-1}function kke(e,t,i,a){for(let n=0;n<i&&YF(e,t);n++){let r=e.readUInt16LE(t+8),s=e.readUInt32LE(t+24),o=e.readUInt16LE(t+28),x=e.readUInt16LE(t+30),A=e.readUInt16LE(t+32),u=e.subarray(t+46,t+46+o).toString(r&2048?"utf8":"latin1");a.push({name:u,encrypted:(r&1)===1,size:s}),t+=46+o+x+A}}function Ml(e){let t=as(String(e).toLowerCase().split(/[/\\]/).pop(),". "),i=t.lastIndexOf(".");return i===-1?"":t.slice(i+1).trim()}function vke(e){return["_VBA_PROJECT","VBA","Macros"].map(i=>T8.Buffer.from(i,"utf16le")).some(i=>e.includes(i))}var Ike=Ge(String.raw`\/(?:JavaScript|JS|Launch|EmbeddedFile)\b`);function xZ(e=[]){let t=[];for(let i of e){let a=typeof i.filename=="string"?i.filename:"",n=a||"unnamed attachment",r=T8.Buffer.isBuffer(i.content)?i.content:null,s=Ml(a),o=r?VF(r):null,x=(E,c,d={})=>t.push({type:E,filename:n,message:c,...d});mke.test(a)&&x("rtl_override",`Attachment "${n}" uses a right-to-left override to hide its real extension`);let A=as(a.toLowerCase(),". ").split(".");A.length>=3&&x5.has(s)&&gke.has(A.at(-2))&&x("double_extension",`Attachment "${n}" has a document extension followed by a program extension`,{extension:s});let u=x5.has(s)&&!N8.has(s);if(o?.executable&&(o.type!=="script"||!u)?x(s&&!x5.has(s)?"disguised_executable":"executable",`Attachment "${n}" contains a program (${o.type})${s&&s!==o.type?` but is named .${s}`:""}`,{extension:o.type}):u&&x("executable",`Attachment "${n}" is a file type that runs code (.${s})`,{extension:s}),!r){N8.has(s)&&x("macro",`Attachment "${n}" is an Office document type that holds macros (.${s})`,{extension:s});continue}if(sZ.has(o?.type)||s==="zip"&&!o?.executable&&GF(r).length>0){let E=GF(r),c=bke.has(o?.type);E.some(B=>/(?:^|\/)vbaproject\.bin$/i.test(B.name))?x("macro",`Attachment "${n}" is an Office document with a VBA macro project`,{extension:s}):N8.has(s)?x("macro",`Attachment "${n}" is an Office document type that holds macros (.${s})`,{extension:s}):c&&E.some(B=>N8.has(Ml(B.name)))&&x("macro",`Attachment "${n}" is an Office document holding another with macros`,{extension:s});let d=E.filter(B=>x5.has(Ml(B.name))&&!(c&&yke.has(Ml(B.name))));d.length>0&&x("executable_in_archive",`Archive "${n}" contains ${d.length===1?"a program":`${d.length} programs`} (${d.slice(0,3).map(B=>B.name.split("/").pop()).join(", ")})`,{extension:Ml(d[0].name),extensions:[...new Set(d.map(B=>Ml(B.name)))]}),(o?.type==="zip"||!sZ.has(o?.type))&&E.some(B=>B.encrypted)&&x("encrypted_archive",`Archive "${n}" is password protected, so its contents cannot be scanned`,{extension:s})}else o?.type==="ole"?vke(r)&&x("macro",`Attachment "${n}" is an Office document with a VBA macro project`,{extension:s}):o?.type==="pdf"||s==="pdf"?Ike.test(r.toString("latin1"))&&x("pdf_active",`PDF "${n}" contains JavaScript, a launch action or an embedded file`,{extension:"pdf"}):o?.type==="rtf"&&/\\object\b|\\objdata\b|\\objupdate\b/.test(r.toString("latin1"))&&x("rtf_object",`RTF document "${n}" embeds an OLE object`,{extension:"rtf"});if(o?.type==="html"||o?.type==="svg"||["htm","html","shtml","xhtml","svg"].includes(s)){let E=r.subarray(0,2e5).toString("utf8"),c=/<script\b|<form\b|\bon(?:load|error|click)\s*=|javascript:|window\.location|document\.location|atob\(/i.test(E);x("html_attachment",`Attachment "${n}" is ${o?.type==="svg"||s==="svg"?"an SVG image":"a web page"}${c?" with scripts or forms":""}`,{extension:s||o?.type,active:c})}}return t}var A5=require("node:buffer"),AZ=require("node:fs"),uZ=bt(require("node:net"),1),Qke=["/var/run/clamav/clamd.ctl","/run/clamav/clamd.ctl","/run/clamd.scan/clamd.sock","/var/run/clamd.scan/clamd.sock","/var/run/clamav/clamd.sock","/tmp/clamd.socket","/opt/homebrew/var/run/clamav/clamd.sock","/usr/local/var/run/clamav/clamd.sock"];function Ske(e={}){if(e.socket)return{path:e.socket};if(e.host||e.port)return{host:e.host||"127.0.0.1",port:Number(e.port)||3310};let t=(e.socketPaths||Qke).find(i=>(0,AZ.existsSync)(i));return t?{path:t}:null}function Nke(e,t,i){return new Promise((a,n)=>{let r=uZ.default.createConnection(e),s=[],o=(x,A)=>{r.destroy(),x?n(x):a(A)};r.setTimeout(i,()=>o(new Error(`clamd did not answer within ${i} ms`))),r.on("connect",()=>t(r)),r.on("data",x=>s.push(x)),r.on("end",()=>o(null,A5.Buffer.concat(s).toString("utf8").replaceAll("\0","").trim())),r.on("error",x=>o(x))})}async function lZ(e,t={}){let i=Ske(t);if(!i)throw new Error("No clamd socket found; set clamav.socket or clamav.host and clamav.port");let a=t.chunkSize??65536,n=await Nke(i,s=>{s.write("zINSTREAM\0");for(let o=0;o<e.length;o+=a){let x=e.subarray(o,o+a),A=A5.Buffer.alloc(4);A.writeUInt32BE(x.length),s.write(A),s.write(x)}s.end(A5.Buffer.alloc(4))},t.timeout??3e4);if(/\bOK$/.test(n))return{infected:!1,viruses:[],reply:n};let r=[...n.matchAll(/:\s*(.+?)\s+FOUND/g)].map(s=>s[1]);if(r.length>0)return{infected:!0,viruses:r,reply:n};throw new Error(`clamd: ${n||"no reply"}`)}var EZ=bt(require("node:dns"),1),u5=require("node:net");var Tke=["1.1.1.2","1.0.0.2"],Rke=["1.1.1.3","1.0.0.3"];function _ke(e,t){let i=(0,u5.isIP)(e);if(i===4)return`${e.split(".").reverse().join(".")}.${t}`;if(i===6){let[a,n=""]=e.split("::"),r=a?a.split(":"):[],s=n?n.split(":"):[];return`${[...[...r,...Array.from({length:8-r.length-s.length}).fill("0"),...s].map(A=>A.padStart(4,"0")).join("")].reverse().join(".")}.${t}`}return null}function cZ(e,t){return!(!/^127(?:\.\d+){3}$/.test(e)||e.startsWith("127.255.255.")||e==="127.0.0.255"||t.endsWith("uribl.com")&&e==="127.0.0.1")}var l5=class{constructor(t={}){this.timeout=t.timeout??3e3,this.servers=t.servers||null,this.cacheSize=t.cacheSize??1e4,this.cacheTtl=t.cacheTtl??6e5,this.cache=new Map,this.resolvers=new Map,this.customResolve4=t.resolve4||null}resolver(t){let i=t?t.join(","):"";if(!this.resolvers.has(i)){let a=new EZ.default.promises.Resolver({timeout:this.timeout,tries:1});t&&a.setServers(t),this.resolvers.set(i,a)}return this.resolvers.get(i)}async resolve4(t,i=this.servers){let a=`${i?i.join(","):""}|${t}`,n=this.cache.get(a);if(n&&n.expires>Date.now())return n.value;let r,s;try{let o=this.customResolve4?this.customResolve4(t,i):this.resolver(i).resolve4(t);r=await Promise.race([o,new Promise(x=>{s=setTimeout(()=>x(null),this.timeout)})])}catch{r=[]}finally{clearTimeout(s)}return r===null?[]:(this.cache.set(a,{value:r,expires:Date.now()+this.cacheTtl}),this.cache.size>this.cacheSize&&this.cache.delete(this.cache.keys().next().value),r)}async checkIp(t,i=[]){return(await Promise.all(i.map(async n=>{let r=_ke(t,n);if(!r)return null;let o=(await this.resolve4(r)).filter(x=>cZ(x,n));return o.length>0?{zone:n,value:t,answers:o}:null}))).filter(Boolean)}async checkDomains(t=[],i=[]){let a=[];for(let r of t)if(!(0,u5.isIP)(r))for(let s of i)a.push((async()=>{let x=(await this.resolve4(`${r}.${s}`)).filter(A=>cZ(A,s));return x.length>0?{zone:s,value:r,answers:x}:null})());return(await Promise.all(a)).filter(Boolean)}async checkCloudflare(t=[],i={}){let{adult:a=!0,malwareServers:n=Tke,familyServers:r=Rke}=i;return(await Promise.all(t.map(async o=>(0,u5.isIP)(o)?null:(await this.resolve4(o,n)).includes("0.0.0.0")?{host:o,category:"malware"}:(a?await this.resolve4(o,r):[]).includes("0.0.0.0")?{host:o,category:"adult"}:null))).filter(Boolean)}};var hZ=require("node:url"),fZ=bt(R5(),1);var Uke=["alipay","auspost","bancolombia","bnpparibas","bradesco","caixabank","canadapost","commerzbank","coupang","creditagricole","deutschebank","flipkart","gosuslugi","japanpost","kakaobank","klarna","kucoin","lazada","mercadolibre","mercadolivre","mercadopago","mercari","natwest","nubank","phonepe","postbank","rakuten","sberbank","swisscom","trustwallet","unicredit","vodafone","wechat","yandex"],Lke=["adobe","airbnb","alibaba","aliexpress","amazon","americanexpress","amex","apple","appleid","att","binance","bankofamerica","barclays","bestbuy","blockchain","booking","bybit","cashapp","chase","citibank","cloudflare","coinbase","comcast","costco","dhl","discord","docusign","dropbox","ebay","facebook","fedex","forwardemail","github","gitlab","gmail","godaddy","google","hmrc","hotmail","hsbc","icloud","instagram","intuit","irs","kraken","ledger","linkedin","mastercard","mcafee","metamask","microsoft","microsoftonline","namecheap","netflix","norton","office","office365","okta","onedrive","outlook","paypal","proton","protonmail","quickbooks","revolut","roblox","royalmail","salesforce","samsung","santander","sharepoint","shopify","skype","slack","sparkasse","spotify","steam","steampowered","stripe","telegram","tiktok","tmobile","trezor","tutanota","twitter","uber","ups","usps","venmo","verizon","visa","walmart","wellsfargo","whatsapp","wise","xfinity","yahoo","zelle","zoom",...Uke],gZ=["account","accounts","alert","alerts","auth","authentication","billing","confirm","login","logon","password","recovery","refund","secure","security","signin","signon","suspended","unlock","update","validate","verification","verify"],dZ=new Set(["apple","blockchain","booking","chase","discord","kraken","ledger","office","proton","slack","steam","stripe"]),Mke=new Set(["akuten","alzada","appel","applied","auslost","azada","coupant","gechat","klara","lanada","lanzada","lapada","latada","lavada","mazada","mercar","mercati","pazada","phoneme","postbak","raketen","rauten","razada","stirpe","vazada","venom","webchat"]),Oke=["adobelogin.com","amazon-adsystem.com","apple-cloudkit.com","apple-dns.net","bnpparibas-am.com","canadapost-postescanada.ca","cdn-apple.com","cloudflare-dns.com","credit-agricole.com","credit-agricole.fr","deutsche-bank.de","google-analytics.com","icloud-content.com","images-amazon.com","instagram-engineering.com","media-amazon.com","okta-emea.com","paypal-community.com","paypal-objects.com","paypal-prepaid.com","paypal-status.com","rakuten-bank.co.jp","rakuten-card.co.jp","rakuten-sec.co.jp","sberbank-ast.ru","slack-edge.com","slack-msgs.com","ssl-images-amazon.com","t-mobile.com","yahoo-corp.jp","yandex-team.ru"],pZ=/^(?:com|net|org|gov|edu)-/,jke=/^(?:co|info|biz|io|app|us|uk|de|fr|es|it|ca|au|jp|br|in)-/,qke=/^(?:co|com)-(?:uk|jp|au|nz|za|in|kr|br)\b/,Hke={\u0430:"a",\u04D3:"a",\u0432:"b",\u0441:"c","\u0501":"d",\u0435:"e",\u0451:"e",\u04BB:"h",\u0456:"i",\u0457:"i",\u0458:"j",\u043A:"k","\u04CF":"l",\u043C:"m",\u043E:"o",\u04E9:"o",\u0440:"p","\u051B":"q",\u0433:"r",\u0455:"s",\u0442:"t",\u0443:"y",\u04AF:"y",\u0445:"x","\u051D":"w",\u044C:"b",\u043F:"n",\u043D:"h",\u03B1:"a",\u03B2:"b",\u03F2:"c",\u03B5:"e",\u03B7:"n",\u03B9:"i",\u03AF:"i",\u03F3:"j",\u03BA:"k",\u03BC:"u",\u03BD:"v",\u03BF:"o",\u03CC:"o",\u03C1:"p",\u03C4:"t",\u03C5:"u",\u03C7:"x",\u03C9:"w",\u03B3:"y",\u0251:"a",\u0261:"g",\u0269:"i",\u0131:"i","\u0237":"j",\u029F:"l",\u0274:"n","\u1D0F":"o",\u0280:"r",\u0475:"v",\u028F:"y","\u1D22":"z","\u2170":"i","\u217C":"l","\u217D":"c","\u217E":"d","\u217F":"m",\u2113:"l","\u0660":"o","\u06F0":"o",\u00E0:"a",\u00E1:"a",\u00E2:"a",\u00E3:"a",\u00E4:"a",\u00E5:"a",\u0101:"a",\u0103:"a",\u0105:"a",\u00E7:"c",\u0107:"c",\u010D:"c",\u010F:"d",\u00E8:"e",\u00E9:"e",\u00EA:"e",\u00EB:"e",\u0113:"e",\u0119:"e",\u011B:"e",\u00EC:"i",\u00ED:"i",\u00EE:"i",\u00EF:"i",\u012B:"i",\u0142:"l",\u00F1:"n",\u0144:"n",\u00F2:"o",\u00F3:"o",\u00F4:"o",\u00F5:"o",\u00F6:"o",\u00F8:"o",\u014D:"o",\u0151:"o",\u0155:"r",\u0159:"r",\u015B:"s",\u0161:"s",\u015F:"s",\u0165:"t",\u0163:"t",\u00F9:"u",\u00FA:"u",\u00FB:"u",\u00FC:"u",\u016B:"u",\u016F:"u",\u0171:"u",\u00FD:"y",\u00FF:"y",\u017A:"z",\u017C:"z",\u017E:"z"},zke=[[/rn/g,"m"],[/vv/g,"w"],[/cl/g,"d"],[/0/g,"o"],[/1/g,"l"],[/3/g,"e"],[/4/g,"a"],[/5/g,"s"],[/7/g,"t"],[/8/g,"b"]];function JF(e){let t="";for(let i of String(e).normalize("NFKC").toLowerCase())t+=Hke[i]||i;return t}var Pke=256;function BZ(e){let t=new Set([e]);for(let[i,a]of zke)if(e.includes(i.source))for(let n of t){if(t.size>=Pke/2)break;t.add(n.replace(i,a))}for(let i of t)t.add(i.replaceAll("l","i"));return t.delete(e),t}function Gke(e,t,i=2){if(Math.abs(e.length-t.length)>i)return i+1;let a=null,n=Array.from({length:t.length+1},(r,s)=>s);for(let r=1;r<=e.length;r++){let s=[r],o=r;for(let x=1;x<=t.length;x++){let A=e[r-1]===t[x-1]?0:1;s[x]=Math.min(n[x]+1,s[x-1]+1,n[x-1]+A),a&&r>1&&x>1&&e[r-1]===t[x-2]&&e[r-2]===t[x-1]&&(s[x]=Math.min(s[x],a[x-2]+1)),o=Math.min(o,s[x])}if(o>i)return i+1;a=n,n=s}return n[t.length]}function Yke(e,t){for(let i of gZ){if(e.length<=i.length+4)continue;let a=e.endsWith(i)?e.slice(0,-i.length):null;if(a&&a.length>=5&&t.has(a)&&!dZ.has(a))return a;let n=e.startsWith(i)?e.slice(i.length):null;if(n&&n.length>=5&&t.has(n)&&!dZ.has(n))return n}return null}function Vke(e){let t=new Set;for(let i of e)new RegExp("\\p{L}","u").test(i)&&t.add(EA(i));return t}var Jke=[new Set(["Han","Hiragana","Katakana","Latin"]),new Set(["Han","Hangul","Latin"])];function Wke(e){return e.size>1&&!Jke.some(t=>[...e].every(i=>t.has(i)))}var R8=class{constructor(t={}){this.options=t,this.brands=new Set([...t.brands||Lke,...t.extraBrands||[]].map(i=>i.toLowerCase())),this.allowlist=new Set([...Oke,...t.allowlist||[]].map(i=>i.toLowerCase())),this.cache=new Map}detectHomographAttack(t){let i=typeof t=="string"?t.trim().toLowerCase().replace(/\.$/,""):"";if(this.cache.has(i))return this.cache.get(i);let a=this.analyse(i);return this.cache.set(i,a),this.cache.size>5e3&&this.cache.delete(this.cache.keys().next().value),a}analyse(t){let i=t&&(0,hZ.domainToUnicode)(t)||t,a={domain:t,unicode:i,isIDN:i!==t||/[^ -~]/.test(i),riskScore:0,riskFactors:[],brand:null,recommendations:[],confidence:0},n=(0,fZ.parse)(i),r=n.domainWithoutSuffix;if(!r||n.isIp||this.allowlist.has(n.domain))return a;let s=(c,d,B)=>{a.riskFactors.push(d),c>a.riskScore&&(a.riskScore=c,a.brand=B)};if(this.brands.has(r))return a;let o=Vke(r),x=JF(r);Wke(o)&&s(.6,`"${i}" mixes ${[...o].join(" and ")} letters`,null);let A=x===r?null:[x,x.replaceAll("-","")].find(c=>this.brands.has(c));if(A)s(.95,`"${i}" imitates ${A} with lookalike letters`,A);else if(/^[a-z\d-]+$/.test(x)){let c=BZ(x),d=[...c].find(f=>this.brands.has(f));if(d)s(.85,`"${i}" imitates ${d} by swapping characters`,d);else if(x.length>=5&&!Mke.has(x)){let f=[...this.brands].find(g=>g.length>=5&&Gke(x,g,1)===1);f&&s(.5,`"${i}" is one letter away from ${f}`,f)}let B=x.includes("-")?x.split("-"):[];for(let f of B){if(f.length<4)continue;if(this.brands.has(f)){s(.6,`"${i}" puts ${f} inside another domain name`,f);break}let g=[...BZ(f)].find(b=>this.brands.has(b));if(g){s(.85,`"${i}" imitates ${g} by swapping characters`,g);break}}for(let f of[x,...B,...c]){let g=Yke(f,this.brands);if(g){s(.6,`"${i}" joins ${g} to a phishing word`,g);break}}}let u=n.subdomain?n.subdomain.split("."):[],l=u.map(c=>JF(c)).find(c=>c.length>=4&&this.brands.has(c));l&&(this.options.strictMode||u.length>=2)&&s(.6,`"${i}" puts ${l} in front of another domain`,l);let E=u.length>0?JF(u.at(-1)):"";if(E.length>=3&&this.brands.has(E)&&(pZ.test(x)||jke.test(x))){let c=pZ.test(x)||qke.test(x)||gZ.some(d=>x.includes(d));s(c?.85:.6,`"${i}" starts with ${E}'s address and continues into another domain`,E)}return a.confidence=a.riskScore,a.riskScore>=.5&&a.recommendations.push(a.brand?`Do not sign in from this link; go to ${a.brand}'s site directly`:"Treat links to this domain as untrusted"),a}};var $ke="XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X",Kke=/cheecck y0ur acc0untt|recorded you|you've been hacked|account is hacked|personal data has leaked|private information has been stolen/i,Zke=/\b(?:ignore|disregard|forget)\s+(?:all\s+|any\s+)?(?:the\s+)?(?:previous|prior|above|earlier|preceding)\s+(?:instructions?|prompts?|rules)\b|\bclassify\s+(?:this|the)\s+(?:e-?mail|message)\s+as\s+(?:ham|safe|legitimate|not\s+spam|clean)\b|\b(?:system|developer)\s+prompt\s*:|\byou\s+are\s+(?:an?\s+)?(?:ai|llm|language model|spam filter|email classifier)\b.{0,80}\b(?:must|should)\b/is,Xke=/^paypal\.(?:[a-z]{2,3}|com?\.[a-z]{2})$/i,eve=new Set(["PPC001017","RT000238","RT000542","RT002947","RTI003384"]),tve=/\binvoice from\b|\bsent you an invoice\b|\b(?:money|payment) request\b|\brequest(?:ed)? (?:money|a payment|for (?:money|payment))\b|\brequested [$€£¥]|\brequested \d|\brechnung von\b|\bzahlungsaufforderung\b|\bgeldanforderung\b|\bfacture de\b|\bdemande d['’]argent\b|\bdemande de paiement\b|\bfactura de\b|\bsolicitud de (?:dinero|pago)\b|\bfattura da\b|\brichiesta di (?:denaro|pagamento)\b|\bfactuur van\b|\bbetaalverzoek\b|\bfatura de\b|\bpedido de (?:dinheiro|pagamento)\b/i,ive=/\bsfv:(?:spm|skb|sks)\b/i,ave=/\bcat:(?:ospm|spm|hspm|phsh|hphsh|hphish|malw|spoof)\b/i,nve=["paypal","apple","icloud","microsoft","outlook","office 365","amazon","netflix","docusign","dhl","fedex","ups","usps","coinbase","binance","metamask","wells fargo","chase","bank of america","american express","facebook","instagram","whatsapp","linkedin","dropbox","adobe","google","gmail","yahoo","norton","mcafee","geek squad","irs","hmrc","royal mail","la poste","sparkasse","santander","hsbc","barclays"];function mZ(e,t){let i=e.headers?.get?.(t);return i==null?"":typeof i=="string"?i:Array.isArray(i)?i.map(a=>typeof a=="string"?a:a?.text??a?.value??"").join(" "):i.text??i.value??String(i)}function CZ(e){let t=String(e),i=t.lastIndexOf("@");return i===-1?"":t.slice(i+1).toLowerCase()}function rve(e={},t={}){let i={...t},a=e.from?.value?.find(n=>n.address)?.address||"";return a&&!i.originalFromAddress&&(i.originalFromAddress=a.toLowerCase()),i.originalFromAddress&&(i.originalFromAddressDomain||=CZ(i.originalFromAddress),i.originalFromAddressRootDomain||=Gt(i.originalFromAddressDomain)),i.resolvedClientHostname&&(i.resolvedClientHostname=i.resolvedClientHostname.toLowerCase().replace(/\.$/,""),i.resolvedRootClientHostname||=Gt(i.resolvedClientHostname)),i}function bZ(e={},t={}){let{threshold:i=5,authentication:a=null,now:n=new Date}=t,r=rve(e,t.session),s=[],o=null,x=(B,f,g,b=null)=>{s.push({name:B,score:f,message:g}),o||=b},A=typeof e.subject=="string"?e.subject:"",u=typeof e.text=="string"?e.text:"",l=typeof e.html=="string"?e.html:"",E=Array.isArray(e.headerLines)?e.headerLines.map(B=>B.line).join(`
482
482
  `):"";if([A,u,l,E].some(B=>B.includes($ke))&&x("GTUBE",1e3,"Contains the GTUBE spam test string","SPAM"),Kke.test(A)&&x("SEXTORTION_SUBJECT",6,"Subject used by sextortion and account takeover scams","SCAM"),Zke.test(`${A}
483
483
  ${u.slice(0,5e4)}
484
- ${l.slice(0,1e5)}`)&&x("PROMPT_INJECTION",3,"Contains instructions addressed to an AI filter","SPAM"),r.resolvedClientHostname?.endsWith(".outbound.protection.outlook.com")){let B=mZ(e,"x-forefront-antispam-report"),f=Number.parseInt(B.match(/\bscl:(-?\d+)/i)?.[1]??"",10);ive.test(B)||ave.test(B)?x("MICROSOFT_SPAM_VERDICT",5,"Microsoft classified this message as spam before relaying it","SPAM"):f>=5&&x("MICROSOFT_HIGH_SCL",3,`Microsoft gave this message a spam confidence level of ${f}`,"SPAM")}if(Xke.test(r.originalFromAddressRootDomain||"")){let B=mZ(e,"x-email-type-id").trim().toUpperCase();(eve.has(B)||tve.test(A))&&x("PAYPAL_INVOICE",6,"PayPal invoice or money request, a channel widely abused for scams","SCAM")}let c=e.from?.value?.find(B=>B.address);if(c){let B=r.originalFromAddressRootDomain,f=typeof c.name=="string"?c.name.toLowerCase():"",g=f.match(P5);g&&Gt(g[1])!==B&&x("FROM_NAME_OTHER_ADDRESS",2.5,`Display name shows ${g[0]} but the message is from ${c.address}`,"SPOOFING");let b=nve.find(k=>new RegExp(`(?:^|[^\\p{L}])${k}(?:$|[^\\p{L}])`,"u").test(f));if(b&&!B.replaceAll("-","").startsWith(b.replaceAll(" ",""))&&x("FROM_NAME_BRAND",2,`Display name says "${b}" but the message is from ${B}`,"PHISHING"),a&&r.envelope?.rcptTo?.length){let k=new Set(r.envelope.rcptTo.map(M=>Gt(CZ(M.address)))),T=a.dmarc?.status?.result==="pass"||a.dkim?.aligned||a.spf?.status?.result==="pass"&&Gt(a.spf.domain||"")===B;k.has(B)&&!T&&x("SELF_SPOOF",3,`Claims to be from the recipient's own domain (${B}) but does not authenticate`,"SPOOFING")}}e.headers?.get&&(e.date?e.date.getTime()-n.getTime()>864e5&&x("DATE_IN_FUTURE",1,"Date header is more than a day in the future"):x("MISSING_DATE",.5,"No Date header"),e.messageId||x("MISSING_MESSAGE_ID",.5,"No Message-ID header"));let d=s.reduce((B,f)=>B+f.score,0);return{isArbitrary:d>=i,score:d,reasons:s.map(B=>B.name),rules:s,category:o}}var DZ=require("node:net");var WF=Object.freeze({isTruthSource:!1,truthSourceValue:null,isAllowlisted:!1,allowlistValue:null,isDenylisted:!1,denylistValue:null});function _8(e){return String(e).trim().toLowerCase().replace(/\.$/,"")}function yZ(e,t){if(!e||t.size===0)return null;let i=_8(e);if(t.has(i))return i;if((0,DZ.isIP)(i))return null;let n=(i.includes("@")?i.slice(i.lastIndexOf("@")+1):i).split(".");for(let r=0;r<n.length-1;r++){let s=n.slice(r).join(".");if(t.has(s))return s}return null}var c5=class{constructor(t={}){this.allowlist=new Set((t.allowlist||[]).map(i=>_8(i))),this.denylist=new Set((t.denylist||[]).map(i=>_8(i))),this.apiUrl=t.apiUrl||null,this.headers=t.headers||{},this.timeout=t.timeout??5e3,this.concurrency=Math.max(1,t.concurrency??8),this.cacheTtl=t.cacheTtl??3e5,this.cacheSize=t.cacheSize??1e4,this.cache=new Map}async lookup(t){let i=_8(t),a=this.cache.get(i);if(a&&a.expires>Date.now())return a.result;let n=WF,r=this.cacheTtl;try{let s=new URL(this.apiUrl);s.searchParams.set("q",i);let o=await W8("GET",s.href,null,{headers:this.headers,timeout:this.timeout,maxResponseBytes:65536});n={isTruthSource:o?.isTruthSource===!0,truthSourceValue:o?.isTruthSource===!0?o.truthSourceValue||i:null,isAllowlisted:o?.isAllowlisted===!0,allowlistValue:o?.isAllowlisted===!0?o.allowlistValue||i:null,isDenylisted:o?.isDenylisted===!0,denylistValue:o?.isDenylisted===!0?o.denylistValue||i:null}}catch(s){n={...WF,error:s.message},r=Math.min(r,6e4)}return this.cache.set(i,{result:n,expires:Date.now()+r}),this.cache.size>this.cacheSize&&this.cache.delete(this.cache.keys().next().value),n}async check(t){let i=[...new Set(t.filter(Boolean).map(n=>_8(n)))],a={...WF,checkedValues:i,details:{}};for(let n of i){let r=yZ(n,this.denylist);r&&!a.isDenylisted&&(a.isDenylisted=!0,a.denylistValue=r);let s=yZ(n,this.allowlist);s&&!a.isAllowlisted&&(a.isAllowlisted=!0,a.allowlistValue=s)}if(this.apiUrl){let n=[...i],r=async()=>{for(;n.length>0;){let s=n.shift(),o=await this.lookup(s);a.details[s]=o;for(let[x,A]of[["isTruthSource","truthSourceValue"],["isAllowlisted","allowlistValue"],["isDenylisted","denylistValue"]])o[x]&&!a[x]&&(a[x]=!0,a[A]=o[A])}};await Promise.all(Array.from({length:Math.min(this.concurrency,i.length)},()=>r()))}return a}};function FZ(e={},t={}){let i=[],a=n=>{if(typeof n!="string"||!n.includes("@"))return;let r=n.toLowerCase(),s=r.slice(r.lastIndexOf("@")+1);i.push(r,s,Gt(s))};t.remoteAddress&&i.push(t.remoteAddress),t.resolvedClientHostname&&i.push(t.resolvedClientHostname,Gt(t.resolvedClientHostname));for(let n of e.from?.value||[])a(n.address);a(t.envelope?.mailFrom?.address);for(let n of e.replyTo?.value||[])a(n.address);return[...new Set(i.filter(Boolean))]}var sve=Math.log10(99)/.8,kZ={bayesSpam:6.25,bayesHam:2.5,homograph:5,mixedScriptDomain:3,brandInDomain:1.5,typoDomain:1,deceptiveLink:3,maliciousDomain:6,adultDomain:2,uriblListed:5,rblListed:4,denylisted:100,allowlisted:-20,truthSource:-5,executable:10,disguisedExecutable:12,doubleExtension:6,rtlOverride:6,executableInArchive:8,encryptedArchive:2,macro:4,pdfActive:3,rtfObject:4,htmlAttachment:1,activeHtmlAttachment:3,virus:100,invisibleCharacters:2,mixedScriptWords:2.5,styledLetters:1.5,languageNotAllowed:3,toxicity:3,nsfw:3,llmSpam:6,llmHam:3},ove=Object.fromEntries([["executable",["EXECUTABLE_ATTACHMENT","executable"]],["disguised_executable",["DISGUISED_EXECUTABLE","disguisedExecutable"]],["double_extension",["DOUBLE_EXTENSION","doubleExtension"]],["rtl_override",["RTL_OVERRIDE_FILENAME","rtlOverride"]],["executable_in_archive",["EXECUTABLE_IN_ARCHIVE","executableInArchive"]],["encrypted_archive",["ENCRYPTED_ARCHIVE","encryptedArchive"]],["macro",["MACRO_ATTACHMENT","macro"]],["pdf_active",["PDF_ACTIVE_CONTENT","pdfActive"]],["rtf_object",["RTF_EMBEDDED_OBJECT","rtfObject"]],["html_attachment",["HTML_ATTACHMENT","htmlAttachment"]]]);function xve(e){let i=[[.999,"999"],[.99,"99"],[.95,"95"],[.8,"80"],[.6,"60"],[.4,"50"],[.2,"40"],[.05,"20"],[.01,"05"]].find(([a])=>e>=a);return`BAYES_${i?i[1]:"00"}`}function wZ(e){return Math.round(e*100)/100}function E5(e={},t={}){let i={...kZ,...t.scores},a=t.threshold??5,n=t.rejectThreshold??15,r=[],s=new Set,o=(B,f,g)=>{let b=Object.hasOwn(i,B)?i[B]:f;s.has(B)||!Number.isFinite(b)||(s.add(B),r.push({name:B,score:wZ(b),description:g}))},{classification:x}=e;if(x&&typeof x.probability=="number"&&x.category!=="disabled"){let B=x.probability,f=xve(B),g=Math.min(Math.abs(Math.log10(B/(1-B)))/sve,1),b=B>.5?i.bayesSpam*g:-i.bayesHam*g;o(f,Math.abs(b)<.005?0:b,`Classifier spam probability ${(B*100).toFixed(1)}%`)}for(let B of e.phishing||[])switch(B.type){case"homograph":{B.riskScore>=.85?o("PHISHING_LOOKALIKE_DOMAIN",i.homograph,B.message):B.riskScore>=.6?o(B.mixedScripts?"MIXED_SCRIPT_DOMAIN":"BRAND_IN_DOMAIN",B.mixedScripts?i.mixedScriptDomain:i.brandInDomain,B.message):o("TYPO_DOMAIN",i.typoDomain,B.message);break}case"deceptive_link":{o("DECEPTIVE_LINK",i.deceptiveLink,B.message);break}case"malicious_domain":{o("MALICIOUS_DOMAIN",i.maliciousDomain,B.message);break}case"adult_domain":{o("ADULT_DOMAIN",i.adultDomain,B.message);break}case"uribl":{o(`URIBL_${B.zone.split(".")[0].toUpperCase()}`,i.uriblListed,B.message);break}default:break}for(let B of e.attachments||[]){let[f,g]=ove[B.type]||[];if(f){let b=B.type==="html_attachment"&&B.active?i.activeHtmlAttachment:i[g];o(f,b,B.message)}}(e.viruses||[]).length>0&&o("VIRUS",i.virus,e.viruses.map(B=>B.message).join("; "));for(let B of e.arbitrary?.rules||[])o(B.name,B.score,B.message);let A=e.obfuscation||{};A.invisible>=3&&o("INVISIBLE_CHARACTERS",i.invisibleCharacters,`${A.invisible} invisible characters inside the text`),A.mixed>=2&&o("MIXED_SCRIPT_WORDS",i.mixedScriptWords,`${A.mixed} words mix letters from different alphabets`),A.styled&&o("STYLED_LETTERS",i.styledLetters,"Uses mathematical or enclosed letters to look like plain text");for(let B of e.authentication?.score?.tests||[])o(B.name,B.score,`Authentication: ${B.name.toLowerCase().replace("_","=")}`);let{reputation:u}=e;u?.isDenylisted?o("DENYLISTED",i.denylisted,`Sender is on the denylist (${u.denylistValue})`):u?.isAllowlisted?o("ALLOWLISTED",i.allowlisted,`Sender is on the allowlist (${u.allowlistValue})`):u?.isTruthSource&&o("TRUTH_SOURCE",i.truthSource,`Sender is a known truth source (${u.truthSourceValue})`);for(let B of e.dnsbl||[])o(`RBL_${B.zone.split(".")[0].toUpperCase()}`,i.rblListed,`Sending IP ${B.value} is listed in ${B.zone}`);e.language?.notAllowed&&o("LANGUAGE_NOT_ALLOWED",i.languageNotAllowed,`Written in ${e.language.language}, which is not an accepted language`),(e.toxicity||[]).length>0&&o("TOXIC_CONTENT",i.toxicity,e.toxicity.map(B=>B.message).join("; ")),(e.nsfw||[]).length>0&&o("NSFW_IMAGE",i.nsfw,e.nsfw.map(B=>B.message).join("; "));let{llm:l}=e,E=(e.arbitrary?.rules||[]).some(B=>B.name==="PROMPT_INJECTION");l?.verdict&&!(E&&l.verdict==="ham")&&(l.verdict==="ham"?o("LLM_HAM",-i.llmHam*l.confidence,`${l.model||l.provider} says ham (${Math.round(l.confidence*100)}%)`):o(`LLM_${l.verdict.toUpperCase()}`,i.llmSpam*l.confidence,`${l.model||l.provider} says ${l.verdict} (${Math.round(l.confidence*100)}%)${l.reasons?.length?`: ${l.reasons.slice(0,2).join("; ")}`:""}`));let c=wZ(r.reduce((B,f)=>B+f.score,0)),d="accept";return c>=n?d="reject":c>=a&&(d="tag"),{score:c,threshold:a,rejectThreshold:n,isSpam:c>=a,action:d,tests:r.filter(B=>B.score!==0||B.name.startsWith("BAYES_"))}}var d5=(0,QZ.debuglog)("spamscanner"),Ave={skipImageLinks:!0,skipTextToHtml:!0,skipTextLinks:!0};function Zr(e){return Object.defineProperty(e,"toString",{value(){return this.message},enumerable:!1}),e}async function Ol(e,t,i){let a;try{return await Promise.race([e,new Promise(n=>{a=setTimeout(()=>n(i),t)})])}finally{clearTimeout(a)}}async function uve(e){if(rA.Buffer.isBuffer(e))return e;if(typeof e=="string")return rA.Buffer.from(e,"utf8");if(e instanceof Uint8Array)return rA.Buffer.from(e);if(e&&typeof e[Symbol.asyncIterator]=="function"){let t=[];for await(let i of e)t.push(rA.Buffer.isBuffer(i)?i:rA.Buffer.from(i));return rA.Buffer.concat(t)}throw new TypeError("A message must be a Buffer, a string, a Uint8Array or a readable stream")}var p5={threshold:5,rejectThreshold:15,scores:{},classifier:void 0,classifierOptions:{},allowedLanguages:[],phishing:{cloudflare:!0,adult:!0,maxHosts:25,homograph:{}},dnsbl:{ip:[],domain:[]},dns:{servers:null,timeout:3e3},attachments:!0,arbitrary:!0,authentication:!1,reputation:!1,clamav:!1,llm:null,toxicity:!1,nsfw:!1,maxLength:1e5,timeout:1e4,session:{}};function vZ(e){let t={...e};if(e.enableAuthentication!==void 0&&t.authentication===void 0&&(t.authentication=e.enableAuthentication?{...e.authOptions}:!1),e.authOptions){let{ip:i,helo:a,sender:n,hostname:r}=e.authOptions;t.session={...i?{remoteAddress:i}:{},...a?{helo:a}:{},...r?{resolvedClientHostname:r}:{},...n?{envelope:{mailFrom:{address:n},rcptTo:[]}}:{},...e.session}}if(e.enableReputation&&e.reputationOptions?.apiUrl&&t.reputation===void 0&&(t.reputation={apiUrl:e.reputationOptions.apiUrl,timeout:e.reputationOptions.timeout}),(e.enableMacroDetection===!1||e.enableArbitraryDetection===!1)&&(e.enableArbitraryDetection===!1&&(t.arbitrary=!1),e.enableMacroDetection===!1&&(t.macros=!1)),e.strictIDNDetection&&(t.phishing={...e.phishing,homograph:{...e.phishing?.homograph,strictMode:!0}}),e.clamscan!==void 0&&t.clamav===void 0){let{clamscan:i}=e;t.clamav=typeof i=="object"&&i?{socket:i.clamdscan?.socket||i.socket,host:i.clamdscan?.host,port:i.clamdscan?.port}:!!i}return(e.allowlist||e.denylist)&&(t.reputation={...typeof t.reputation=="object"?t.reputation:{},allowlist:e.allowlist,denylist:e.denylist}),t}var lve=1e3,$a=class{constructor(t={}){let i=vZ(t);this.config={...p5,...i,phishing:{...p5.phishing,...i.phishing},dnsbl:{...p5.dnsbl,...i.dnsbl},dns:{...p5.dns,...i.dns}},this.classifier=null,this.homograph=new R8(this.config.phishing.homograph),this.dns=new l5({servers:this.config.dns.servers,timeout:this.config.dns.timeout,resolve4:this.config.dns.resolve4}),this.reputation=this.config.reputation?new c5(this.config.reputation):null,this.llm=this.config.llm?new fA(this.config.llm):null,this.metrics={totalScans:0,averageTime:0,lastScanTime:0};for(let a of["toxicity","nsfw"]){let n=this.config[a];if(n&&typeof n.model?.classify!="function")throw new TypeError(`The ${a} check needs { model } with a classify() method; see https://spamscanner.net/docs/api/#${a}`)}}getClassifier(){if(this.classifier||this.config.classifier===!1)return this.classifier;let t=this.config.classifier;return t instanceof A0?this.classifier=t:typeof t=="string"?this.classifier=Ox(t,this.config.classifierOptions):t&&typeof t=="object"?this.classifier=A0.fromJSON(t,this.config.classifierOptions):this.classifier=Bl(this.config.classifierOptions),this.classifier}async parse(t){let i=await uve(t),a=await(0,SZ.simpleParser)(i,Ave);return{raw:i,mail:a}}getFeatures(t){return pA(t,{maxLength:this.config.maxLength})}getTokens(t,i){return ho(t,{locale:i,maxLength:this.config.maxLength}).words}async getTokensAndMailFromSource(t){let{mail:i}=await this.parse(t),{words:a,features:n}=this.getFeatures(i);return{tokens:a,features:n,mail:i}}getClassification(t){let i=this.getClassifier();return i?i.classify(t):{category:"disabled",probability:.5,spam:.5,ham:.5,clues:[]}}async learn(t,i){let{mail:a}=await this.parse(t);this.getClassifier()||(this.classifier=new A0(this.config.classifierOptions)),this.classifier.learn(this.getFeatures(a).features,i)}async unlearn(t,i){let{mail:a}=await this.parse(t),n=this.getClassifier();n&&n.unlearn(this.getFeatures(a).features,i)}saveModel(t,i){let a=this.getClassifier()||new A0(this.config.classifierOptions);t8(a,t,i)}async scanFile(t,i){return this.scan(await(0,IZ.readFile)(t),i)}async checkLinks(t,i,a=[]){let n=[],r=[],s=new Set,o=new Set,x=(c,d)=>{if(o.has(c)||o.size>=lve)return;o.add(c);let B=this.homograph.detectHomographAttack(c);if(d.source==="text"?B.riskScore>=.95||B.riskScore>=.85&&B.brand.length>=5:B.riskScore>=.5){let g=Zr({type:"homograph",...d,domain:B.domain,unicode:B.unicode,brand:B.brand,riskScore:B.riskScore,riskFactors:B.riskFactors,mixedScripts:B.riskFactors.some(b=>b.includes(" mixes ")),message:B.riskFactors.at(-1)});n.push(g),r.push(g)}};for(let c of t)dA(c)&&n.push(Zr({type:"deceptive_link",url:c.url,text:c.text,message:`A link shows "${c.text}" but goes to ${c.host}`})),s.add(c.host);for(let c of[...t.filter(d=>d.text),...t.filter(d=>!d.text)])x(c.host,{url:c.url});for(let c of a)x(c,{source:"text"});let A=[...s].slice(0,i.phishing.maxHosts),u=[...new Set(A.map(c=>Gt(c)))],[l,E]=await Promise.all([i.phishing.cloudflare&&A.length>0?Ol(this.dns.checkCloudflare(A,{adult:i.phishing.adult}),i.timeout,[]):[],i.dnsbl.domain?.length>0&&u.length>0?Ol(this.dns.checkDomains(u,i.dnsbl.domain),i.timeout,[]):[]]);for(let{host:c,category:d}of l)n.push(Zr(d==="malware"?{type:"malicious_domain",domain:c,message:`Link hostname ${c} is blocked by Cloudflare's malware filtering as phishing or malware`}:{type:"adult_domain",domain:c,message:`Link hostname ${c} was detected by Cloudflare's family filtering to contain adult-related content`}));for(let{zone:c,value:d}of E)n.push(Zr({type:"uribl",domain:d,zone:c,message:`Link domain ${d} is listed in ${c}`}));return{phishing:n,homographs:r}}async scanViruses(t,i){let a=i.clamav===!0?{}:i.clamav,n=[];return await Promise.all(t.attachments.map(async r=>{try{let s=await lZ(r.content,{timeout:i.timeout,...a});s.infected&&n.push(Zr({type:"virus",filename:r.filename||"unnamed attachment",virus:s.viruses,message:`Attachment "${r.filename||"unnamed attachment"}" contains ${s.viruses.join(", ")}`}))}catch(s){d5("virus scan failed: %s",s.message)}})),n}async scanToxicity(t,i){let{model:a,threshold:n=.7}=i.toxicity,r=[t.subject,t.text].filter(Boolean).join(`
484
+ ${l.slice(0,1e5)}`)&&x("PROMPT_INJECTION",3,"Contains instructions addressed to an AI filter","SPAM"),r.resolvedClientHostname?.endsWith(".outbound.protection.outlook.com")){let B=mZ(e,"x-forefront-antispam-report"),f=Number.parseInt(B.match(/\bscl:(-?\d+)/i)?.[1]??"",10);ive.test(B)||ave.test(B)?x("MICROSOFT_SPAM_VERDICT",5,"Microsoft classified this message as spam before relaying it","SPAM"):f>=5&&x("MICROSOFT_HIGH_SCL",3,`Microsoft gave this message a spam confidence level of ${f}`,"SPAM")}if(Xke.test(r.originalFromAddressRootDomain||"")){let B=mZ(e,"x-email-type-id").trim().toUpperCase();(eve.has(B)||tve.test(A))&&x("PAYPAL_INVOICE",6,"PayPal invoice or money request, a channel widely abused for scams","SCAM")}let c=e.from?.value?.find(B=>B.address);if(c){let B=r.originalFromAddressRootDomain,f=typeof c.name=="string"?c.name.toLowerCase():"",g=f.match(P5);g&&Gt(g[1])!==B&&x("FROM_NAME_OTHER_ADDRESS",2.5,`Display name shows ${g[0]} but the message is from ${c.address}`,"SPOOFING");let b=nve.find(k=>new RegExp(`(?:^|[^\\p{L}])${k}(?:$|[^\\p{L}])`,"u").test(f));if(b&&!B.replaceAll("-","").startsWith(b.replaceAll(" ",""))&&x("FROM_NAME_BRAND",2,`Display name says "${b}" but the message is from ${B}`,"PHISHING"),a&&r.envelope?.rcptTo?.length){let k=new Set(r.envelope.rcptTo.map(M=>Gt(CZ(M.address)))),T=a.dmarc?.status?.result==="pass"||a.dkim?.aligned||a.spf?.status?.result==="pass"&&Gt(a.spf.domain||"")===B;k.has(B)&&!T&&x("SELF_SPOOF",3,`Claims to be from the recipient's own domain (${B}) but does not authenticate`,"SPOOFING")}}e.headers?.get&&(e.date?e.date.getTime()-n.getTime()>864e5&&x("DATE_IN_FUTURE",1,"Date header is more than a day in the future"):x("MISSING_DATE",.5,"No Date header"),e.messageId||x("MISSING_MESSAGE_ID",.5,"No Message-ID header"));let d=s.reduce((B,f)=>B+f.score,0);return{isArbitrary:d>=i,score:d,reasons:s.map(B=>B.name),rules:s,category:o}}var DZ=require("node:net");var WF=Object.freeze({isTruthSource:!1,truthSourceValue:null,isAllowlisted:!1,allowlistValue:null,isDenylisted:!1,denylistValue:null});function _8(e){return String(e).trim().toLowerCase().replace(/\.$/,"")}function yZ(e,t){if(!e||t.size===0)return null;let i=_8(e);if(t.has(i))return i;if((0,DZ.isIP)(i))return null;let n=(i.includes("@")?i.slice(i.lastIndexOf("@")+1):i).split(".");for(let r=0;r<n.length-1;r++){let s=n.slice(r).join(".");if(t.has(s))return s}return null}var c5=class{constructor(t={}){this.allowlist=new Set((t.allowlist||[]).map(i=>_8(i))),this.denylist=new Set((t.denylist||[]).map(i=>_8(i))),this.apiUrl=t.apiUrl||null,this.headers=t.headers||{},this.timeout=t.timeout??5e3,this.concurrency=Math.max(1,t.concurrency??8),this.cacheTtl=t.cacheTtl??3e5,this.cacheSize=t.cacheSize??1e4,this.cache=new Map}async lookup(t){let i=_8(t),a=this.cache.get(i);if(a&&a.expires>Date.now())return a.result;let n=WF,r=this.cacheTtl;try{let s=new URL(this.apiUrl);s.searchParams.set("q",i);let o=await W8("GET",s.href,null,{headers:this.headers,timeout:this.timeout,maxResponseBytes:65536});n={isTruthSource:o?.isTruthSource===!0,truthSourceValue:o?.isTruthSource===!0?o.truthSourceValue||i:null,isAllowlisted:o?.isAllowlisted===!0,allowlistValue:o?.isAllowlisted===!0?o.allowlistValue||i:null,isDenylisted:o?.isDenylisted===!0,denylistValue:o?.isDenylisted===!0?o.denylistValue||i:null}}catch(s){n={...WF,error:s.message},r=Math.min(r,6e4)}return this.cache.set(i,{result:n,expires:Date.now()+r}),this.cache.size>this.cacheSize&&this.cache.delete(this.cache.keys().next().value),n}async check(t){let i=[...new Set(t.filter(Boolean).map(n=>_8(n)))],a={...WF,checkedValues:i,details:{}};for(let n of i){let r=yZ(n,this.denylist);r&&!a.isDenylisted&&(a.isDenylisted=!0,a.denylistValue=r);let s=yZ(n,this.allowlist);s&&!a.isAllowlisted&&(a.isAllowlisted=!0,a.allowlistValue=s)}if(this.apiUrl){let n=[...i],r=async()=>{for(;n.length>0;){let s=n.shift(),o=await this.lookup(s);a.details[s]=o;for(let[x,A]of[["isTruthSource","truthSourceValue"],["isAllowlisted","allowlistValue"],["isDenylisted","denylistValue"]])o[x]&&!a[x]&&(a[x]=!0,a[A]=o[A])}};await Promise.all(Array.from({length:Math.min(this.concurrency,i.length)},()=>r()))}return a}};function FZ(e={},t={}){let i=[],a=n=>{if(typeof n!="string"||!n.includes("@"))return;let r=n.toLowerCase(),s=r.slice(r.lastIndexOf("@")+1);i.push(r,s,Gt(s))};t.remoteAddress&&i.push(t.remoteAddress),t.resolvedClientHostname&&i.push(t.resolvedClientHostname,Gt(t.resolvedClientHostname));for(let n of e.from?.value||[])a(n.address);a(t.envelope?.mailFrom?.address);for(let n of e.replyTo?.value||[])a(n.address);return[...new Set(i.filter(Boolean))]}var sve=Math.log10(99)/.8,kZ={bayesSpam:6.25,bayesHam:2.5,homograph:5,mixedScriptDomain:3,brandInDomain:1.5,typoDomain:1,deceptiveLink:3,maliciousDomain:6,adultDomain:2,uriblListed:5,rblListed:4,denylisted:100,allowlisted:-20,truthSource:-5,executable:10,disguisedExecutable:12,doubleExtension:6,rtlOverride:6,executableInArchive:8,encryptedArchive:2,macro:4,pdfActive:3,rtfObject:4,htmlAttachment:1,activeHtmlAttachment:3,virus:100,invisibleCharacters:2,mixedScriptWords:2.5,styledLetters:1.5,languageNotAllowed:3,toxicity:3,nsfw:3,llmSpam:6,llmHam:3},ove=Object.fromEntries([["executable",["EXECUTABLE_ATTACHMENT","executable"]],["disguised_executable",["DISGUISED_EXECUTABLE","disguisedExecutable"]],["double_extension",["DOUBLE_EXTENSION","doubleExtension"]],["rtl_override",["RTL_OVERRIDE_FILENAME","rtlOverride"]],["executable_in_archive",["EXECUTABLE_IN_ARCHIVE","executableInArchive"]],["encrypted_archive",["ENCRYPTED_ARCHIVE","encryptedArchive"]],["macro",["MACRO_ATTACHMENT","macro"]],["pdf_active",["PDF_ACTIVE_CONTENT","pdfActive"]],["rtf_object",["RTF_EMBEDDED_OBJECT","rtfObject"]],["html_attachment",["HTML_ATTACHMENT","htmlAttachment"]]]);function xve(e){let i=[[.999,"999"],[.99,"99"],[.95,"95"],[.8,"80"],[.6,"60"],[.4,"50"],[.2,"40"],[.05,"20"],[.01,"05"]].find(([a])=>e>=a);return`BAYES_${i?i[1]:"00"}`}function wZ(e){return Math.round(e*100)/100}function E5(e={},t={}){let i={...kZ,...t.scores},a=t.threshold??5,n=t.rejectThreshold??15,r=[],s=new Set,o=(B,f,g)=>{let b=Object.hasOwn(i,B)?i[B]:f;s.has(B)||!Number.isFinite(b)||(s.add(B),r.push({name:B,score:wZ(b),description:g}))},{classification:x}=e;if(x&&typeof x.probability=="number"&&x.category!=="disabled"){let B=x.probability,f=xve(B),g=Math.min(Math.abs(Math.log10(B/(1-B)))/sve,1),b=B>.5?i.bayesSpam*g:-i.bayesHam*g;o(f,Math.abs(b)<.005?0:b,`Classifier spam probability ${(B*100).toFixed(1)}%`)}for(let B of e.phishing||[])switch(B.type){case"homograph":{B.riskScore>=.85?o("PHISHING_LOOKALIKE_DOMAIN",i.homograph,B.message):B.riskScore>=.6?o(B.mixedScripts?"MIXED_SCRIPT_DOMAIN":"BRAND_IN_DOMAIN",B.mixedScripts?i.mixedScriptDomain:i.brandInDomain,B.message):o("TYPO_DOMAIN",i.typoDomain,B.message);break}case"deceptive_link":{o("DECEPTIVE_LINK",i.deceptiveLink,B.message);break}case"malicious_domain":{o("MALICIOUS_DOMAIN",i.maliciousDomain,B.message);break}case"adult_domain":{o("ADULT_DOMAIN",i.adultDomain,B.message);break}case"uribl":{o(`URIBL_${B.zone.split(".")[0].toUpperCase()}`,i.uriblListed,B.message);break}default:break}for(let B of e.attachments||[]){let[f,g]=ove[B.type]||[];if(f){let b=B.type==="html_attachment"&&B.active?i.activeHtmlAttachment:i[g];o(f,b,B.message)}}(e.viruses||[]).length>0&&o("VIRUS",i.virus,e.viruses.map(B=>B.message).join("; "));for(let B of e.arbitrary?.rules||[])o(B.name,B.score,B.message);let A=e.obfuscation||{};A.invisible>=3&&o("INVISIBLE_CHARACTERS",i.invisibleCharacters,`${A.invisible} invisible characters inside the text`),A.mixed>=2&&o("MIXED_SCRIPT_WORDS",i.mixedScriptWords,`${A.mixed} words mix letters from different alphabets`),A.styled&&o("STYLED_LETTERS",i.styledLetters,"Uses mathematical or enclosed letters to look like plain text");for(let B of e.authentication?.score?.tests||[])o(B.name,B.score,`Authentication: ${B.name.toLowerCase().replace("_","=")}`);let{reputation:u}=e;u?.isDenylisted?o("DENYLISTED",i.denylisted,`Sender is on the denylist (${u.denylistValue})`):u?.isAllowlisted?o("ALLOWLISTED",i.allowlisted,`Sender is on the allowlist (${u.allowlistValue})`):u?.isTruthSource&&o("TRUTH_SOURCE",i.truthSource,`Sender is a known truth source (${u.truthSourceValue})`);for(let B of e.dnsbl||[])o(`RBL_${B.zone.split(".")[0].toUpperCase()}`,i.rblListed,`Sending IP ${B.value} is listed in ${B.zone}`);e.language?.notAllowed&&o("LANGUAGE_NOT_ALLOWED",i.languageNotAllowed,`Written in ${e.language.language}, which is not an accepted language`),(e.toxicity||[]).length>0&&o("TOXIC_CONTENT",i.toxicity,e.toxicity.map(B=>B.message).join("; ")),(e.nsfw||[]).length>0&&o("NSFW_IMAGE",i.nsfw,e.nsfw.map(B=>B.message).join("; "));let{llm:l}=e,E=(e.arbitrary?.rules||[]).some(B=>B.name==="PROMPT_INJECTION");l?.verdict&&!(E&&l.verdict==="ham")&&(l.verdict==="ham"?o("LLM_HAM",-i.llmHam*l.confidence,`${l.model||l.provider} says ham (${Math.round(l.confidence*100)}%)`):o(`LLM_${l.verdict.toUpperCase()}`,i.llmSpam*l.confidence,`${l.model||l.provider} says ${l.verdict} (${Math.round(l.confidence*100)}%)${l.reasons?.length?`: ${l.reasons.slice(0,2).join("; ")}`:""}`));let c=wZ(r.reduce((B,f)=>B+f.score,0)),d="accept";return c>=n?d="reject":c>=a&&(d="tag"),{score:c,threshold:a,rejectThreshold:n,isSpam:c>=a,action:d,tests:r.filter(B=>B.score!==0||B.name.startsWith("BAYES_"))}}var d5=(0,QZ.debuglog)("spamscanner"),Ave={skipImageLinks:!0,skipTextToHtml:!0,skipTextLinks:!0};function Zr(e){return Object.defineProperty(e,"toString",{value(){return this.message},enumerable:!1}),e}async function Ol(e,t,i){let a;try{return await Promise.race([e,new Promise(n=>{a=setTimeout(()=>n(i),t)})])}finally{clearTimeout(a)}}async function uve(e){if(rA.Buffer.isBuffer(e))return e;if(typeof e=="string")return rA.Buffer.from(e,"utf8");if(e instanceof Uint8Array)return rA.Buffer.from(e);if(e&&typeof e[Symbol.asyncIterator]=="function"){let t=[];for await(let i of e)t.push(rA.Buffer.isBuffer(i)?i:rA.Buffer.from(i));return rA.Buffer.concat(t)}throw new TypeError("A message must be a Buffer, a string, a Uint8Array or a readable stream")}var p5={threshold:5,rejectThreshold:15,scores:{},classifier:void 0,classifierOptions:{},allowedLanguages:[],phishing:{cloudflare:!0,adult:!0,maxHosts:25,homograph:{}},dnsbl:{ip:[],domain:[]},dns:{servers:null,timeout:3e3},attachments:!0,arbitrary:!0,authentication:!1,reputation:!1,clamav:!1,llm:null,toxicity:!1,nsfw:!1,maxLength:1e5,timeout:1e4,session:{}};function vZ(e){let t={...e};if(e.enableAuthentication!==void 0&&t.authentication===void 0&&(t.authentication=e.enableAuthentication?{...e.authOptions}:!1),e.authOptions){let{ip:i,helo:a,sender:n,hostname:r}=e.authOptions;t.session={...i?{remoteAddress:i}:{},...a?{helo:a}:{},...r?{resolvedClientHostname:r}:{},...n?{envelope:{mailFrom:{address:n},rcptTo:[]}}:{},...e.session}}if(e.enableReputation&&e.reputationOptions?.apiUrl&&t.reputation===void 0&&(t.reputation={apiUrl:e.reputationOptions.apiUrl,timeout:e.reputationOptions.timeout}),(e.enableMacroDetection===!1||e.enableArbitraryDetection===!1)&&(e.enableArbitraryDetection===!1&&(t.arbitrary=!1),e.enableMacroDetection===!1&&(t.macros=!1)),e.strictIDNDetection&&(t.phishing={...e.phishing,homograph:{...e.phishing?.homograph,strictMode:!0}}),e.clamscan!==void 0&&t.clamav===void 0){let{clamscan:i}=e;t.clamav=typeof i=="object"&&i?{socket:i.clamdscan?.socket||i.socket,host:i.clamdscan?.host,port:i.clamdscan?.port}:!!i}return(e.allowlist||e.denylist)&&(t.reputation={...typeof t.reputation=="object"?t.reputation:{},allowlist:e.allowlist,denylist:e.denylist}),t}var lve=1e3,$a=class{constructor(t={}){let i=vZ(t);this.config={...p5,...i,phishing:{...p5.phishing,...i.phishing},dnsbl:{...p5.dnsbl,...i.dnsbl},dns:{...p5.dns,...i.dns}},this.classifier=null,this.homograph=new R8(this.config.phishing.homograph),this.dns=new l5({servers:this.config.dns.servers,timeout:this.config.dns.timeout,resolve4:this.config.dns.resolve4}),this.reputation=this.config.reputation?new c5(this.config.reputation):null,this.llm=this.config.llm?new fA(this.config.llm):null,this.metrics={totalScans:0,averageTime:0,lastScanTime:0};for(let a of["toxicity","nsfw"]){let n=this.config[a];if(n&&typeof n.model?.classify!="function")throw new TypeError(`The ${a} check needs { model } with a classify() method; see https://spamscanner.net/docs/api/#${a}`)}}getClassifier(){if(this.classifier||this.config.classifier===!1)return this.classifier;let t=this.config.classifier;return t instanceof A0?this.classifier=t:typeof t=="string"?this.classifier=Ox(t,this.config.classifierOptions):t&&typeof t=="object"?this.classifier=A0.fromJSON(t,this.config.classifierOptions):this.classifier=Bl(this.config.classifierOptions),this.classifier}async parse(t){let i=await uve(t),a=await(0,SZ.simpleParser)(i,Ave);return{raw:i,mail:a}}getFeatures(t){return pA(t,{maxLength:this.config.maxLength})}getTokens(t,i){return ho(t,{locale:i,maxLength:this.config.maxLength}).words}async getTokensAndMailFromSource(t){let{mail:i}=await this.parse(t),{words:a,features:n}=this.getFeatures(i);return{tokens:a,features:n,mail:i}}getClassification(t){let i=this.getClassifier();return i?i.classify(t):{category:"disabled",probability:.5,spam:.5,ham:.5,clues:[]}}async learn(t,i){let{mail:a}=await this.parse(t);this.getClassifier()||(this.classifier=new A0(this.config.classifierOptions)),this.classifier.learn(this.getFeatures(a).features,i)}async unlearn(t,i){let{mail:a}=await this.parse(t),n=this.getClassifier();n&&n.unlearn(this.getFeatures(a).features,i)}saveModel(t,i){let a=this.getClassifier()||new A0(this.config.classifierOptions);t8(a,t,i)}async scanFile(t,i){return this.scan(await(0,IZ.readFile)(t),i)}async checkLinks(t,i,a=[]){let n=[],r=[],s=new Set,o=new Set,x=(c,d)=>{if(o.has(c)||o.size>=lve)return;o.add(c);let B=this.homograph.detectHomographAttack(c);if(d.source==="text"?B.riskScore>=.95||B.riskScore>=.85&&B.brand.length>=5:B.riskScore>=.5){let g=Zr({type:"homograph",...d,domain:B.domain,unicode:B.unicode,brand:B.brand,riskScore:B.riskScore,riskFactors:B.riskFactors,mixedScripts:B.riskFactors.some(b=>b.includes(" mixes ")),message:B.riskFactors.at(-1)});n.push(g),r.push(g)}};for(let c of t)dA(c)&&n.push(Zr({type:"deceptive_link",url:c.url,text:c.text,message:`A link shows "${c.text}" but goes to ${c.host}`})),s.add(c.host);for(let c of[...t.filter(d=>d.text),...t.filter(d=>!d.text)])x(c.host,{url:c.url});for(let c of a)x(c,{source:"text"});let A=[...s].slice(0,i.phishing.maxHosts),u=[...new Set(A.map(c=>Gt(c)))],[l,E]=await Promise.all([i.phishing.cloudflare&&A.length>0?Ol(this.dns.checkCloudflare(A,{adult:i.phishing.adult}),i.timeout,[]):[],i.dnsbl.domain?.length>0&&u.length>0?Ol(this.dns.checkDomains(u,i.dnsbl.domain),i.timeout,[]):[]]);for(let{host:c,category:d}of l)n.push(Zr(d==="malware"?{type:"malicious_domain",domain:c,message:`Link hostname ${c} is blocked by Cloudflare's malware filtering as phishing or malware`}:{type:"adult_domain",domain:c,message:`Link hostname ${c} was detected by Cloudflare's family filtering to contain adult-related content`}));for(let{zone:c,value:d}of E)n.push(Zr({type:"uribl",domain:d,zone:c,message:`Link domain ${d} is listed in ${c}`}));return{phishing:n,homographs:r,uncheckedHosts:s.size-A.length}}async scanViruses(t,i){let a=i.clamav===!0?{}:i.clamav,n=[];return await Promise.all(t.attachments.map(async r=>{try{let s=await lZ(r.content,{timeout:i.timeout,...a});s.infected&&n.push(Zr({type:"virus",filename:r.filename||"unnamed attachment",virus:s.viruses,message:`Attachment "${r.filename||"unnamed attachment"}" contains ${s.viruses.join(", ")}`}))}catch(s){d5("virus scan failed: %s",s.message)}})),n}async scanToxicity(t,i){let{model:a,threshold:n=.7}=i.toxicity,r=[t.subject,t.text].filter(Boolean).join(`
485
485
  `).slice(0,5e3);if(r.trim().length<10)return[];try{return(await Ol(Promise.resolve(a.classify([r])),i.timeout,[])).map(o=>({label:o.label,probability:o.results?.[0]?.probabilities?.[1]??0})).filter(o=>o.probability>=n).map(o=>Zr({type:"toxicity",category:o.label,probability:o.probability,message:`Toxic content: ${o.label} (${(o.probability*100).toFixed(1)}%)`}))}catch(s){return d5("toxicity check failed: %s",s.message),[]}}async scanNsfw(t,i){let{model:a,threshold:n=.6}=i.nsfw,r=t.attachments.filter(o=>["png","jpg","gif","webp"].includes(VF(o.content)?.type)),s=[];for(let o of r)try{let A=(await Ol(Promise.resolve(a.classify(o.content)),i.timeout,[])).find(u=>["Porn","Hentai","Sexy"].includes(u.className)&&u.probability>=n);A&&s.push(Zr({type:"nsfw",filename:o.filename||"unnamed image",category:A.className,probability:A.probability,message:`Image "${o.filename||"unnamed image"}" looks like ${A.className.toLowerCase()} content (${(A.probability*100).toFixed(1)}%)`}))}catch(x){d5("NSFW check failed: %s",x.message)}return s}async scan(t,i={}){let a=Date.now(),n=vZ(i),r={...this.config,...n,phishing:{...this.config.phishing,...n.phishing},dnsbl:{...this.config.dnsbl,...n.dnsbl},session:{...this.config.session,...n.session}},{session:s}=r,{raw:o,mail:x}=await this.parse(t),A=this.getFeatures(x),u=this.getClassification(A.features),l=Ek(`${x.subject||""}
486
- ${typeof x.text=="string"?x.text:""}`,{maxLength:r.maxLength}),E=r.attachments===!1?[]:xZ(x.attachments).filter(Z=>r.macros!==!1||!["macro","pdf_active","rtf_object"].includes(Z.type)).map(Z=>Zr(Z)),[c,d,B,f,g,b,k]=await Promise.all([this.checkLinks(A.links,r,A.hosts),r.authenticationResults&&typeof r.authenticationResults=="object"?vb(r.authenticationResults):r.authentication&&s.remoteAddress?DY(o,{ip:s.remoteAddress,helo:s.helo,sender:s.envelope?.mailFrom?.address,timeout:r.timeout,...typeof r.authentication=="object"?r.authentication:{}}):null,this.reputation?Ol(this.reputation.check(FZ(x,s)),r.timeout,null):null,s.remoteAddress&&r.dnsbl.ip?.length>0?Ol(this.dns.checkIp(s.remoteAddress,r.dnsbl.ip),r.timeout,[]):[],r.clamav?this.scanViruses(x,r):[],r.toxicity?this.scanToxicity(x,r):[],r.nsfw?this.scanNsfw(x,r):[]]);d&&(d.score=FY(d,typeof r.authentication=="object"?r.authentication.weights:void 0));let T=r.arbitrary===!1?{rules:[],score:0,reasons:[]}:bZ(x,{session:s,authentication:d}),M=(r.allowedLanguages||[]).map(Z=>Z.toLowerCase()),_={language:A.language,script:A.script,notAllowed:!!(M.length>0&&A.language&&!M.includes(A.language))},L={classification:u,phishing:c.phishing,attachments:E,viruses:g,arbitrary:T,obfuscation:l,authentication:d,reputation:B,dnsbl:f,language:_,toxicity:b,nsfw:k,llm:null},q={scores:r.scores,threshold:r.threshold,rejectThreshold:r.rejectThreshold},G=E5(L,q),we=r.llm&&r.llm!==this.config.llm?new fA(r.llm):this.llm;if(we){let Z=r.llm,Ce=Z.mode||"auto",Qe=Z.minScore??r.threshold-4,Se=Z.maxScore??r.rejectThreshold,z=G.score>=Qe&&G.score<Se;if(Ce==="always"||Ce==="auto"&&(z||u.category==="unsure"||u.category==="disabled")){try{L.llm=await we.classify(x,{links:A.links,text:A.text,hidden:A.hidden,authentication:d?kY(d):null})}catch(Ee){d5("LLM check failed: %s",Ee.message),L.llm={verdict:null,error:Ee.message,provider:we.config.provider,model:we.config.model}}G=E5(L,q)}}let Ue=G.tests.filter(Z=>Z.score>0).sort((Z,Ce)=>Ce.score-Z.score).map(Z=>Z.name),qe=Date.now()-a;return this.metrics.totalScans++,this.metrics.lastScanTime=qe,this.metrics.averageTime+=(qe-this.metrics.averageTime)/this.metrics.totalScans,{isSpam:G.isSpam,score:G.score,threshold:G.threshold,rejectThreshold:G.rejectThreshold,action:G.action,message:G.isSpam?`Spam (${Ue.slice(0,5).join(", ")})`:"Ham",tests:G.tests,results:{...L,arbitrary:T.rules.filter(Z=>Z.score>=r.threshold).map(Z=>Zr({type:"arbitrary",...Z})),executables:E.filter(Z=>["executable","disguised_executable","double_extension","rtl_override","executable_in_archive"].includes(Z.type)),macros:E.filter(Z=>["macro","pdf_active","rtf_object"].includes(Z.type)),idnHomographAttack:{detected:c.homographs.length>0,domains:c.homographs,riskScore:Math.max(0,...c.homographs.map(Z=>Z.riskScore))}},links:A.links.map(Z=>Z.url),language:A.language,tokens:A.words,mail:x,version:Q0,metrics:{totalTime:qe}}}};var cve=`Spam Scanner ${Q0}
486
+ ${typeof x.text=="string"?x.text:""}`,{maxLength:r.maxLength}),E=r.attachments===!1?[]:xZ(x.attachments).filter(Z=>r.macros!==!1||!["macro","pdf_active","rtf_object"].includes(Z.type)).map(Z=>Zr(Z)),[c,d,B,f,g,b,k]=await Promise.all([this.checkLinks(A.links,r,A.hosts),r.authenticationResults&&typeof r.authenticationResults=="object"?vb(r.authenticationResults):r.authentication&&s.remoteAddress?DY(o,{ip:s.remoteAddress,helo:s.helo,sender:s.envelope?.mailFrom?.address,timeout:r.timeout,...typeof r.authentication=="object"?r.authentication:{}}):null,this.reputation?Ol(this.reputation.check(FZ(x,s)),r.timeout,null):null,s.remoteAddress&&r.dnsbl.ip?.length>0?Ol(this.dns.checkIp(s.remoteAddress,r.dnsbl.ip),r.timeout,[]):[],r.clamav?this.scanViruses(x,r):[],r.toxicity?this.scanToxicity(x,r):[],r.nsfw?this.scanNsfw(x,r):[]]);d&&(d.score=FY(d,typeof r.authentication=="object"?r.authentication.weights:void 0));let T=r.arbitrary===!1?{rules:[],score:0,reasons:[]}:bZ(x,{session:s,authentication:d}),M=(r.allowedLanguages||[]).map(Z=>Z.toLowerCase()),_={language:A.language,script:A.script,notAllowed:!!(M.length>0&&A.language&&!M.includes(A.language))},L={classification:u,phishing:c.phishing,attachments:E,viruses:g,arbitrary:T,obfuscation:l,authentication:d,reputation:B,dnsbl:f,language:_,toxicity:b,nsfw:k,llm:null},q={scores:r.scores,threshold:r.threshold,rejectThreshold:r.rejectThreshold},G=E5(L,q),we=r.llm&&r.llm!==this.config.llm?new fA(r.llm):this.llm;if(we){let Z=r.llm,Ce=Z.mode||"auto",Qe=Z.minScore??r.threshold-4,Se=Z.maxScore??r.rejectThreshold,z=G.score>=Qe&&G.score<Se;if(Ce==="always"||Ce==="auto"&&(z||u.category==="unsure"||u.category==="disabled")){try{L.llm=await we.classify(x,{links:A.links,text:A.text,hidden:A.hidden,authentication:d?kY(d):null})}catch(Ee){d5("LLM check failed: %s",Ee.message),L.llm={verdict:null,error:Ee.message,provider:we.config.provider,model:we.config.model}}G=E5(L,q)}}let Ue=G.tests.filter(Z=>Z.score>0).sort((Z,Ce)=>Ce.score-Z.score).map(Z=>Z.name),qe=Date.now()-a;return this.metrics.totalScans++,this.metrics.lastScanTime=qe,this.metrics.averageTime+=(qe-this.metrics.averageTime)/this.metrics.totalScans,{isSpam:G.isSpam,score:G.score,threshold:G.threshold,rejectThreshold:G.rejectThreshold,action:G.action,message:G.isSpam?`Spam (${Ue.slice(0,5).join(", ")})`:"Ham",tests:G.tests,results:{...L,arbitrary:T.rules.filter(Z=>Z.score>=r.threshold).map(Z=>Zr({type:"arbitrary",...Z})),executables:E.filter(Z=>["executable","disguised_executable","double_extension","rtl_override","executable_in_archive"].includes(Z.type)),macros:E.filter(Z=>["macro","pdf_active","rtf_object"].includes(Z.type)),uncheckedHosts:c.uncheckedHosts,idnHomographAttack:{detected:c.homographs.length>0,domains:c.homographs,riskScore:Math.max(0,...c.homographs.map(Z=>Z.riskScore))}},links:A.links.map(Z=>Z.url),language:A.language,tokens:A.words,mail:x,version:Q0,metrics:{totalTime:qe}}}};var cve=`Spam Scanner ${Q0}
487
487
 
488
488
  Usage: spamscanner <command> [options]
489
489
 
@@ -132,6 +132,7 @@ export class SpamScanner {
132
132
  checkLinks(links: any, config: any, hosts?: any[]): Promise<{
133
133
  phishing: any[];
134
134
  homographs: any[];
135
+ uncheckedHosts: number;
135
136
  }>;
136
137
  scanViruses(mail: any, config: any): Promise<any[]>;
137
138
  scanToxicity(mail: any, config: any): Promise<any>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spamscanner",
3
3
  "description": "Spam, phishing and malware filter for email in any language: a trainable classifier, link and attachment checks, SPF/DKIM/DMARC, and an optional AI second opinion from any LLM, including local Ollama models. Works as a library, CLI, Postfix milter or HTTP API.",
4
- "version": "7.0.2",
4
+ "version": "7.0.3",
5
5
  "author": "Forward Email (https://forwardemail.net)",
6
6
  "bin": {
7
7
  "spamscanner": "./dist/esm/cli.js"