spamscanner 6.0.0 → 6.1.0

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../replacement-words.json", "../../replacements.js", "../../get-classifier.js", "../../src/enhanced-idn-detector.js", "../../src/cli.js", "../../src/index.js", "../../src/auth.js", "../../src/reputation.js", "../../src/is-arbitrary.js", "../../src/get-attributes.js"],
4
+ "sourcesContent": ["[\n \"url\",\n \"email\",\n \"number\",\n \"currency\",\n \"initialism\",\n \"abbreviation\",\n \"emoji\",\n \"hexa\",\n \"mac\",\n \"phone\",\n \"bitcoin\",\n \"cc\"\n]\n", "import {debuglog} from 'node:util';\nimport {readFileSync} from 'node:fs';\nimport cryptoRandomString from 'crypto-random-string';\nimport REPLACEMENT_WORDS from './replacement-words.json' with { type: 'json' };\n\nconst debug = debuglog('spamscanner');\n\nconst randomOptions = {\n\tlength: 10,\n\tcharacters: 'abcdefghijklmnopqrstuvwxyz',\n};\n\n// Simply delete the replacements.json to generate new replacements\nlet replacements = {};\ntry {\n\treplacements = JSON.parse(readFileSync('./replacements.json', 'utf8'));\n} catch (error) {\n\tdebug(error);\n\tfor (const replacement of REPLACEMENT_WORDS) {\n\t\treplacements[replacement] = `${replacement}${cryptoRandomString(randomOptions)}`;\n\t}\n}\n\nexport default replacements;\n", "import {debuglog} from 'node:util';\nimport {readFileSync} from 'node:fs';\nimport NaiveBayes from '@ladjs/naivebayes';\n\nconst debug = debuglog('spamscanner');\n\nlet classifier = new NaiveBayes().toJsonObject();\n\ntry {\n\tclassifier = JSON.parse(readFileSync('./classifier.json', 'utf8'));\n} catch (error) {\n\tdebug(error);\n}\n\nexport default classifier;\n", "#!/usr/bin/env node\n/**\n * Enhanced IDN Homograph Attack Detection\n * Based on comprehensive research and best practices\n */\n\nimport {createHash} from 'node:crypto';\nimport confusables from 'confusables';\n\n// Unicode confusable character mappings (subset for demonstration)\nconst CONFUSABLE_CHARS = new Map([\n\t// Cyrillic to Latin confusables\n\t['\u0430', 'a'],\n\t['\u0435', 'e'],\n\t['\u043E', 'o'],\n\t['\u0440', 'p'],\n\t['\u0441', 'c'],\n\t['\u0445', 'x'],\n\t['\u0443', 'y'],\n\t['\u0410', 'A'],\n\t['\u0412', 'B'],\n\t['\u0415', 'E'],\n\t['\u041A', 'K'],\n\t['\u041C', 'M'],\n\t['\u041D', 'H'],\n\t['\u041E', 'O'],\n\t['\u0420', 'P'],\n\t['\u0421', 'C'],\n\t['\u0422', 'T'],\n\t['\u0425', 'X'],\n\t['\u0423', 'Y'],\n\n\t// Greek to Latin confusables\n\t['\u03B1', 'a'],\n\t['\u03BF', 'o'],\n\t['\u03C1', 'p'],\n\t['\u03C5', 'u'],\n\t['\u03BD', 'v'],\n\t['\u03B9', 'i'],\n\t['\u0391', 'A'],\n\t['\u0392', 'B'],\n\t['\u0395', 'E'],\n\t['\u0396', 'Z'],\n\t['\u0397', 'H'],\n\t['\u0399', 'I'],\n\t['\u039A', 'K'],\n\t['\u039C', 'M'],\n\t['\u039D', 'N'],\n\t['\u039F', 'O'],\n\t['\u03A1', 'P'],\n\t['\u03A4', 'T'],\n\t['\u03A5', 'Y'],\n\n\t// Mathematical symbols\n\t['\uD835\uDC1A', 'a'],\n\t['\uD835\uDC1B', 'b'],\n\t['\uD835\uDC1C', 'c'],\n\t['\uD835\uDC1D', 'd'],\n\t['\uD835\uDC1E', 'e'],\n\t['\uD835\uDFCE', '0'],\n\t['\uD835\uDFCF', '1'],\n\t['\uD835\uDFD0', '2'],\n\t['\uD835\uDFD1', '3'],\n\t['\uD835\uDFD2', '4'],\n\n\t// Other common confusables\n\t['\u212F', 'e'],\n\t['\u210A', 'g'],\n\t['\u210E', 'h'],\n\t['\u2113', 'l'],\n\t['\u2134', 'o'],\n\t['\u212F', 'e'],\n\t['\u2170', 'i'],\n\t['\u2171', 'ii'],\n\t['\u2172', 'iii'],\n\t['\u2173', 'iv'],\n\t['\u2174', 'v'],\n]);\n\n// Known legitimate international domains (whitelist approach)\nconst LEGITIMATE_IDN_DOMAINS = new Set([\n\t'xn--fsq.xn--0zwm56d', // \u4E2D\u56FD\n\t'xn--fiqs8s', // \u4E2D\u56FD\n\t'xn--fiqz9s', // \u4E2D\u56EF\n\t'xn--j6w193g', // \u9999\u6E2F\n\t'xn--55qx5d', // \u516C\u53F8\n\t'xn--io0a7i', // \u7F51\u7EDC\n\t// Add more legitimate domains as needed\n]);\n\n// Popular brand domains for comparison\nconst POPULAR_BRANDS = [\n\t'google',\n\t'facebook',\n\t'amazon',\n\t'apple',\n\t'microsoft',\n\t'twitter',\n\t'instagram',\n\t'linkedin',\n\t'youtube',\n\t'netflix',\n\t'paypal',\n\t'ebay',\n\t'yahoo',\n\t'adobe',\n\t'salesforce',\n\t'oracle',\n\t'ibm',\n\t'cisco',\n\t'intel',\n\t'nvidia',\n\t'tesla',\n\t'citibank',\n\t'bankofamerica',\n\t'wellsfargo',\n\t'chase',\n\t'americanexpress',\n];\n\nclass EnhancedIDNDetector {\n\tconstructor(options = {}) {\n\t\tthis.options = {\n\t\t\tstrictMode: false,\n\t\t\tenableWhitelist: true,\n\t\t\tenableBrandProtection: true,\n\t\t\tenableContextAnalysis: true,\n\t\t\tmaxSimilarityThreshold: 0.8,\n\t\t\tminDomainAge: 30, // Days\n\t\t\t...options,\n\t\t};\n\n\t\tthis.cache = new Map();\n\t}\n\n\t/**\n\t * Main detection method with comprehensive analysis\n\t */\n\tdetectHomographAttack(domain, context = {}) {\n\t\tconst cacheKey = this.getCacheKey(domain, context);\n\t\tif (this.cache.has(cacheKey)) {\n\t\t\treturn this.cache.get(cacheKey);\n\t\t}\n\n\t\tconst result = this.analyzeComprehensive(domain, context);\n\t\tthis.cache.set(cacheKey, result);\n\t\treturn result;\n\t}\n\n\t/**\n\t * Comprehensive analysis combining multiple detection methods\n\t */\n\tanalyzeComprehensive(domain, context) {\n\t\tconst analysis = {\n\t\t\tdomain,\n\t\t\tisIDN: this.isIDNDomain(domain),\n\t\t\triskScore: 0,\n\t\t\triskFactors: [],\n\t\t\trecommendations: [],\n\t\t\tconfidence: 0,\n\t\t};\n\n\t\t// Skip analysis for whitelisted domains\n\t\tif (this.options.enableWhitelist && this.isWhitelisted(domain)) {\n\t\t\tanalysis.riskScore = 0;\n\t\t\tanalysis.confidence = 1;\n\t\t\tanalysis.recommendations.push('Domain is whitelisted as legitimate');\n\t\t\treturn analysis;\n\t\t}\n\n\t\t// Basic IDN detection\n\t\tif (analysis.isIDN) {\n\t\t\tanalysis.riskScore += 0.3;\n\t\t\tanalysis.riskFactors.push('Contains non-ASCII characters');\n\t\t}\n\n\t\t// Confusable character analysis\n\t\tconst confusableAnalysis = this.analyzeConfusableCharacters(domain);\n\t\tanalysis.riskScore += confusableAnalysis.score;\n\t\tanalysis.riskFactors.push(...confusableAnalysis.factors);\n\n\t\t// Brand similarity analysis\n\t\tif (this.options.enableBrandProtection) {\n\t\t\tconst brandAnalysis = this.analyzeBrandSimilarity(domain);\n\t\t\tanalysis.riskScore += brandAnalysis.score;\n\t\t\tanalysis.riskFactors.push(...brandAnalysis.factors);\n\t\t}\n\n\t\t// Script mixing analysis\n\t\tconst scriptAnalysis = this.analyzeScriptMixing(domain);\n\t\tanalysis.riskScore += scriptAnalysis.score;\n\t\tanalysis.riskFactors.push(...scriptAnalysis.factors);\n\n\t\t// Context analysis\n\t\tif (this.options.enableContextAnalysis && context) {\n\t\t\tconst contextAnalysis = this.analyzeContext(domain, context);\n\t\t\tanalysis.riskScore += contextAnalysis.score;\n\t\t\tanalysis.riskFactors.push(...contextAnalysis.factors);\n\t\t}\n\n\t\t// Punycode analysis\n\t\tif (domain.includes('xn--')) {\n\t\t\tconst punycodeAnalysis = this.analyzePunycode(domain);\n\t\t\tanalysis.riskScore += punycodeAnalysis.score;\n\t\t\tanalysis.riskFactors.push(...punycodeAnalysis.factors);\n\t\t}\n\n\t\t// Calculate final confidence and recommendations\n\t\tanalysis.confidence = Math.min(analysis.riskScore, 1);\n\t\tanalysis.recommendations = this.generateRecommendations(analysis);\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Detect if domain contains IDN characters\n\t */\n\tisIDNDomain(domain) {\n\t\t// eslint-disable-next-line no-control-regex\n\t\treturn domain.includes('xn--') || /[^\\u0000-\\u007F]/.test(domain);\n\t}\n\n\t/**\n\t * Check if domain is in whitelist\n\t */\n\tisWhitelisted(domain) {\n\t\tconst normalized = domain.toLowerCase();\n\t\treturn LEGITIMATE_IDN_DOMAINS.has(normalized);\n\t}\n\n\t/**\n\t * Analyze confusable characters\n\t */\n\tanalyzeConfusableCharacters(domain) {\n\t\tconst analysis = {score: 0, factors: []};\n\t\tlet confusableCount = 0;\n\t\tlet totalChars = 0;\n\n\t\t// Use confusables library to detect and normalize\n\t\ttry {\n\t\t\tconst normalized = confusables(domain);\n\t\t\tif (normalized !== domain) {\n\t\t\t\t// Domain contains confusable characters\n\t\t\t\tfor (const char of domain) {\n\t\t\t\t\ttotalChars++;\n\t\t\t\t\tconst normalizedChar = confusables(char);\n\t\t\t\t\tif (normalizedChar !== char) {\n\t\t\t\t\t\tconfusableCount++;\n\t\t\t\t\t\tanalysis.factors.push(`Confusable character: ${char} \u2192 ${normalizedChar}`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (confusableCount > 0) {\n\t\t\t\t\tconst ratio = confusableCount / totalChars;\n\t\t\t\t\tanalysis.score = Math.min(ratio * 0.8, 0.6);\n\t\t\t\t\tanalysis.factors.push(`${confusableCount}/${totalChars} characters are confusable`, `Normalized domain: ${normalized}`);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Fallback to manual detection\n\t\t\tfor (const char of domain) {\n\t\t\t\ttotalChars++;\n\t\t\t\tif (CONFUSABLE_CHARS.has(char)) {\n\t\t\t\t\tconfusableCount++;\n\t\t\t\t\tanalysis.factors.push(`Confusable character: ${char} \u2192 ${CONFUSABLE_CHARS.get(char)}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (confusableCount > 0) {\n\t\t\t\tconst ratio = confusableCount / totalChars;\n\t\t\t\tanalysis.score = Math.min(ratio * 0.8, 0.6);\n\t\t\t\tanalysis.factors.push(`${confusableCount}/${totalChars} characters are confusable`);\n\t\t\t}\n\t\t}\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Analyze similarity to popular brands\n\t */\n\tanalyzeBrandSimilarity(domain) {\n\t\tconst analysis = {score: 0, factors: []};\n\t\tconst cleanDomain = this.normalizeDomain(domain);\n\n\t\tfor (const brand of POPULAR_BRANDS) {\n\t\t\tconst similarity = this.calculateSimilarity(cleanDomain, brand);\n\t\t\tif (similarity > this.options.maxSimilarityThreshold) {\n\t\t\t\tanalysis.score = Math.max(analysis.score, similarity * 0.7);\n\t\t\t\tanalysis.factors.push(`High similarity to ${brand}: ${(similarity * 100).toFixed(1)}%`);\n\t\t\t}\n\t\t}\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Analyze script mixing patterns\n\t */\n\tanalyzeScriptMixing(domain) {\n\t\tconst analysis = {score: 0, factors: []};\n\t\tconst scripts = this.detectScripts(domain);\n\n\t\tif (scripts.size > 1) {\n\t\t\t// Mixed scripts can be suspicious\n\t\t\tconst scriptList = [...scripts].join(', ');\n\t\t\tanalysis.factors.push(`Mixed scripts detected: ${scriptList}`);\n\n\t\t\t// Higher risk for certain combinations\n\t\t\tif (scripts.has('Latin') && (scripts.has('Cyrillic') || scripts.has('Greek'))) {\n\t\t\t\tanalysis.score += 0.4;\n\t\t\t\tanalysis.factors.push('Suspicious Latin/Cyrillic or Latin/Greek mixing');\n\t\t\t} else {\n\t\t\t\tanalysis.score += 0.2;\n\t\t\t}\n\t\t}\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Analyze context (email headers, content, etc.)\n\t */\n\tanalyzeContext(domain, context) {\n\t\tconst analysis = {score: 0, factors: []};\n\n\t\t// Check if display text differs from actual domain\n\t\tif (context.displayText && context.displayText !== domain) {\n\t\t\tanalysis.score += 0.3;\n\t\t\tanalysis.factors.push('Display text differs from actual domain');\n\t\t}\n\n\t\t// Check sender reputation\n\t\tif (context.senderReputation && context.senderReputation < 0.5) {\n\t\t\tanalysis.score += 0.2;\n\t\t\tanalysis.factors.push('Low sender reputation');\n\t\t}\n\n\t\t// Check for suspicious email patterns\n\t\tif (context.emailContent) {\n\t\t\tconst suspiciousPatterns = [\n\t\t\t\t/urgent/i,\n\t\t\t\t/verify.*account/i,\n\t\t\t\t/suspended/i,\n\t\t\t\t/click.*here/i,\n\t\t\t\t/limited.*time/i,\n\t\t\t\t/act.*now/i,\n\t\t\t\t/confirm.*identity/i,\n\t\t\t];\n\n\t\t\tfor (const pattern of suspiciousPatterns) {\n\t\t\t\tif (pattern.test(context.emailContent)) {\n\t\t\t\t\tanalysis.score += 0.1;\n\t\t\t\t\tanalysis.factors.push(`Suspicious email pattern: ${pattern.source}`);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Analyze punycode domains\n\t */\n\tanalyzePunycode(domain) {\n\t\tconst analysis = {score: 0, factors: []};\n\n\t\ttry {\n\t\t\t// Decode punycode to see actual characters\n\t\t\tconst decoded = this.decodePunycode(domain);\n\t\t\tanalysis.factors.push(`Punycode decoded: ${decoded}`);\n\n\t\t\t// Check if decoded version looks suspicious\n\t\t\tconst decodedAnalysis = this.analyzeConfusableCharacters(decoded);\n\t\t\tanalysis.score += decodedAnalysis.score * 0.8;\n\t\t\tanalysis.factors.push(...decodedAnalysis.factors);\n\t\t} catch {\n\t\t\tanalysis.score += 0.2;\n\t\t\tanalysis.factors.push('Invalid punycode encoding');\n\t\t}\n\n\t\treturn analysis;\n\t}\n\n\t/**\n\t * Normalize domain for comparison\n\t */\n\tnormalizeDomain(domain) {\n\t\tlet normalized = domain.toLowerCase();\n\n\t\t// Use confusables library to remove confusable characters\n\t\ttry {\n\t\t\tnormalized = confusables(normalized);\n\t\t} catch {\n\t\t\t// Fallback to manual replacement if confusables fails\n\t\t\tfor (const [confusable, latin] of CONFUSABLE_CHARS) {\n\t\t\t\tnormalized = normalized.replaceAll(confusable, latin);\n\t\t\t}\n\t\t}\n\n\t\t// Remove common TLD for comparison\n\t\tnormalized = normalized.replace(/\\.(com|org|net|edu|gov)$/, '');\n\n\t\treturn normalized;\n\t}\n\n\t/**\n\t * Calculate string similarity using Levenshtein distance\n\t */\n\tcalculateSimilarity(string1, string2) {\n\t\tconst matrix = [];\n\t\tconst length1 = string1.length;\n\t\tconst length2 = string2.length;\n\n\t\tfor (let i = 0; i <= length2; i++) {\n\t\t\tmatrix[i] = [i];\n\t\t}\n\n\t\tfor (let j = 0; j <= length1; j++) {\n\t\t\tmatrix[0][j] = j;\n\t\t}\n\n\t\tfor (let i = 1; i <= length2; i++) {\n\t\t\tfor (let j = 1; j <= length1; j++) {\n\t\t\t\tif (string2.charAt(i - 1) === string1.charAt(j - 1)) {\n\t\t\t\t\tmatrix[i][j] = matrix[i - 1][j - 1];\n\t\t\t\t} else {\n\t\t\t\t\tmatrix[i][j] = Math.min(\n\t\t\t\t\t\tmatrix[i - 1][j - 1] + 1,\n\t\t\t\t\t\tmatrix[i][j - 1] + 1,\n\t\t\t\t\t\tmatrix[i - 1][j] + 1,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst maxLength = Math.max(length1, length2);\n\t\treturn maxLength === 0 ? 1 : (maxLength - matrix[length2][length1]) / maxLength;\n\t}\n\n\t/**\n\t * Detect scripts used in domain\n\t */\n\tdetectScripts(domain) {\n\t\tconst scripts = new Set();\n\n\t\tfor (const char of domain) {\n\t\t\tconst code = char.codePointAt(0);\n\n\t\t\tif ((code >= 0x00_41 && code <= 0x00_5A) || (code >= 0x00_61 && code <= 0x00_7A)) {\n\t\t\t\tscripts.add('Latin');\n\t\t\t} else if (code >= 0x04_00 && code <= 0x04_FF) {\n\t\t\t\tscripts.add('Cyrillic');\n\t\t\t} else if (code >= 0x03_70 && code <= 0x03_FF) {\n\t\t\t\tscripts.add('Greek');\n\t\t\t} else if (code >= 0x4E_00 && code <= 0x9F_FF) {\n\t\t\t\tscripts.add('CJK');\n\t\t\t} else if (code >= 0x05_90 && code <= 0x05_FF) {\n\t\t\t\tscripts.add('Hebrew');\n\t\t\t} else if (code >= 0x06_00 && code <= 0x06_FF) {\n\t\t\t\tscripts.add('Arabic');\n\t\t\t}\n\t\t}\n\n\t\treturn scripts;\n\t}\n\n\t/**\n\t * Simple punycode decoder (basic implementation)\n\t */\n\tdecodePunycode(domain) {\n\t\t// This is a simplified implementation\n\t\t// In production, use a proper punycode library\n\t\ttry {\n\t\t\tconst url = new URL(`http://${domain}`);\n\t\t\treturn url.hostname;\n\t\t} catch {\n\t\t\treturn domain;\n\t\t}\n\t}\n\n\t/**\n\t * Generate recommendations based on analysis\n\t */\n\tgenerateRecommendations(analysis) {\n\t\tconst recommendations = [];\n\n\t\tif (analysis.riskScore > 0.8) {\n\t\t\trecommendations.push('HIGH RISK: Likely homograph attack - block or quarantine');\n\t\t} else if (analysis.riskScore > 0.6) {\n\t\t\trecommendations.push('MEDIUM RISK: Suspicious domain - flag for review');\n\t\t} else if (analysis.riskScore > 0.3) {\n\t\t\trecommendations.push('LOW RISK: Monitor domain activity');\n\t\t} else {\n\t\t\trecommendations.push('SAFE: Domain appears legitimate');\n\t\t}\n\n\t\tif (analysis.isIDN) {\n\t\t\trecommendations.push('Consider displaying punycode representation to users');\n\t\t}\n\n\t\tif (analysis.riskFactors.some(f => f.includes('brand'))) {\n\t\t\trecommendations.push('Verify domain authenticity through official channels');\n\t\t}\n\n\t\treturn recommendations;\n\t}\n\n\t/**\n\t * Generate cache key\n\t */\n\tgetCacheKey(domain, context) {\n\t\tconst contextHash = createHash('md5')\n\t\t\t.update(JSON.stringify(context))\n\t\t\t.digest('hex')\n\t\t\t.slice(0, 8);\n\t\treturn `${domain}:${contextHash}`;\n\t}\n}\n\nexport default EnhancedIDNDetector;\n\n", "/***\n * SpamScanner CLI\n *\n * Command-line interface for scanning emails for spam, phishing, and malware.\n * Can be used standalone or integrated with mail servers like Postfix and Dovecot.\n *\n * Exit codes:\n * 0 - Clean (not spam)\n * 1 - Spam detected\n * 2 - Error occurred\n */\n\nimport {Buffer} from 'node:buffer';\nimport {\n\tcreateReadStream, readFileSync, writeFileSync, existsSync, mkdirSync,\n} from 'node:fs';\nimport {createServer} from 'node:net';\nimport {homedir} from 'node:os';\nimport path from 'node:path';\nimport process from 'node:process';\nimport {fileURLToPath} from 'node:url';\nimport SpamScanner from './index.js';\n\n// Get version from package.json\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Supported languages with their ISO 639-1 codes\n */\nconst SUPPORTED_LANGUAGES = {\n\ten: 'English',\n\tfr: 'French',\n\tes: 'Spanish',\n\tde: 'German',\n\tit: 'Italian',\n\tpt: 'Portuguese',\n\tru: 'Russian',\n\tja: 'Japanese',\n\tko: 'Korean',\n\tzh: 'Chinese',\n\tar: 'Arabic',\n\thi: 'Hindi',\n\tbn: 'Bengali',\n\tur: 'Urdu',\n\ttr: 'Turkish',\n\tpl: 'Polish',\n\tnl: 'Dutch',\n\tsv: 'Swedish',\n\tno: 'Norwegian',\n\tda: 'Danish',\n\tfi: 'Finnish',\n\thu: 'Hungarian',\n\tcs: 'Czech',\n\tsk: 'Slovak',\n\tsl: 'Slovenian',\n\thr: 'Croatian',\n\tsr: 'Serbian',\n\tbg: 'Bulgarian',\n\tro: 'Romanian',\n\tel: 'Greek',\n\the: 'Hebrew',\n\tth: 'Thai',\n\tvi: 'Vietnamese',\n\tid: 'Indonesian',\n\tms: 'Malay',\n\ttl: 'Tagalog',\n\tuk: 'Ukrainian',\n\tbe: 'Belarusian',\n\tlt: 'Lithuanian',\n\tlv: 'Latvian',\n\tet: 'Estonian',\n\tca: 'Catalan',\n\teu: 'Basque',\n\tgl: 'Galician',\n\tga: 'Irish',\n\tgd: 'Scottish Gaelic',\n\tcy: 'Welsh',\n\tis: 'Icelandic',\n\tmt: 'Maltese',\n\taf: 'Afrikaans',\n\tsw: 'Swahili',\n\tam: 'Amharic',\n\tha: 'Hausa',\n\tyo: 'Yoruba',\n\tig: 'Igbo',\n\tso: 'Somali',\n\tom: 'Oromo',\n\tti: 'Tigrinya',\n\tmg: 'Malagasy',\n\tny: 'Chichewa',\n\tsn: 'Shona',\n\txh: 'Xhosa',\n\tzu: 'Zulu',\n\tst: 'Southern Sotho',\n\ttn: 'Tswana',\n};\n\n/**\n * Default score weights for different detection types\n */\nconst DEFAULT_SCORES = {\n\tclassifier: 5, // Base score when classifier says spam\n\tphishing: 5, // Per phishing issue detected\n\texecutable: 10, // Per dangerous executable detected\n\tmacro: 5, // Per macro detected\n\tvirus: 100, // Per virus detected\n\tnsfw: 3, // Per NSFW content detected\n\ttoxicity: 3, // Per toxic content detected\n};\n\n/**\n * Update check cache file location\n */\nconst UPDATE_CACHE_DIR = path.join(homedir(), '.spamscanner');\nconst UPDATE_CACHE_FILE = path.join(UPDATE_CACHE_DIR, 'update-check.json');\nconst UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * Find the package.json by traversing up from current directory\n * @returns {string} Version string\n */\nfunction getVersion() {\n\t// Try multiple possible locations\n\tconst possiblePaths = [\n\t\tpath.join(__dirname, '..', 'package.json'),\n\t\tpath.join(__dirname, '..', '..', 'package.json'),\n\t\tpath.join(__dirname, '..', '..', '..', 'package.json'),\n\t];\n\n\tfor (const pkgPath of possiblePaths) {\n\t\ttry {\n\t\t\tconst content = readFileSync(pkgPath, 'utf8');\n\t\t\tconst pkg = JSON.parse(content);\n\t\t\tif (pkg.name === 'spamscanner' && pkg.version) {\n\t\t\t\treturn pkg.version;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Continue to next path\n\t\t}\n\t}\n\n\treturn 'unknown';\n}\n\nconst VERSION = getVersion();\n\n/**\n * Compare two semver versions\n * @param {string} v1 - First version\n * @param {string} v2 - Second version\n * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2\n */\nfunction compareVersions(v1, v2) {\n\tconst parts1 = v1.replace(/^v/, '').split('.').map(Number);\n\tconst parts2 = v2.replace(/^v/, '').split('.').map(Number);\n\n\tfor (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {\n\t\tconst p1 = parts1[i] || 0;\n\t\tconst p2 = parts2[i] || 0;\n\t\tif (p1 < p2) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (p1 > p2) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\n/**\n * Get the platform-specific binary name\n * @returns {string} Binary name for current platform\n */\nfunction getBinaryName() {\n\tconst {platform} = process;\n\tconst {arch} = process;\n\n\tif (platform === 'win32') {\n\t\treturn 'spamscanner-win-x64.exe';\n\t}\n\n\tif (platform === 'darwin') {\n\t\treturn arch === 'arm64' ? 'spamscanner-darwin-arm64' : 'spamscanner-darwin-x64';\n\t}\n\n\treturn 'spamscanner-linux-x64';\n}\n\n/**\n * Check for updates from GitHub releases\n * @param {boolean} force - Force check even if recently checked\n * @returns {Promise<object|null>} Update info or null if up to date\n */\nasync function checkForUpdates(force = false) {\n\ttry {\n\t\t// Check cache first (unless forced)\n\t\tif (!force && existsSync(UPDATE_CACHE_FILE)) {\n\t\t\tconst cache = JSON.parse(readFileSync(UPDATE_CACHE_FILE, 'utf8'));\n\t\t\tconst age = Date.now() - cache.timestamp;\n\t\t\tif (age < UPDATE_CHECK_INTERVAL) {\n\t\t\t\t// Return cached result\n\t\t\t\tif (cache.latestVersion && compareVersions(cache.latestVersion, VERSION) > 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcurrentVersion: VERSION,\n\t\t\t\t\t\tlatestVersion: cache.latestVersion,\n\t\t\t\t\t\treleaseUrl: cache.releaseUrl,\n\t\t\t\t\t\tdownloadUrl: cache.downloadUrl,\n\t\t\t\t\t\tcached: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\t// Fetch latest release from GitHub API\n\t\tconst response = await fetch('https://api.github.com/repos/spamscanner/spamscanner/releases/latest', {\n\t\t\theaders: {\n\t\t\t\tAccept: 'application/vnd.github.v3+json',\n\t\t\t\t'User-Agent': `spamscanner-cli/${VERSION}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst release = await response.json();\n\t\tconst latestVersion = release.tag_name.replace(/^v/, '');\n\n\t\t// Find the download URL for current platform\n\t\tconst binaryName = getBinaryName();\n\t\tconst asset = release.assets.find(a => a.name === binaryName);\n\t\tconst downloadUrl = asset?.browser_download_url;\n\n\t\t// Cache the result\n\t\tconst cacheData = {\n\t\t\ttimestamp: Date.now(),\n\t\t\tlatestVersion,\n\t\t\treleaseUrl: release.html_url,\n\t\t\tdownloadUrl,\n\t\t};\n\n\t\ttry {\n\t\t\tif (!existsSync(UPDATE_CACHE_DIR)) {\n\t\t\t\tmkdirSync(UPDATE_CACHE_DIR, {recursive: true});\n\t\t\t}\n\n\t\t\twriteFileSync(UPDATE_CACHE_FILE, JSON.stringify(cacheData, null, 2));\n\t\t} catch {\n\t\t\t// Ignore cache write errors\n\t\t}\n\n\t\t// Check if update is available\n\t\tif (compareVersions(latestVersion, VERSION) > 0) {\n\t\t\treturn {\n\t\t\t\tcurrentVersion: VERSION,\n\t\t\t\tlatestVersion,\n\t\t\t\treleaseUrl: release.html_url,\n\t\t\t\tdownloadUrl,\n\t\t\t\tcached: false,\n\t\t\t};\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Print update notification if available\n * @param {boolean} force - Force check even if recently checked\n */\nasync function printUpdateNotification(force = false) {\n\tconst update = await checkForUpdates(force);\n\tif (update) {\n\t\tconst {platform} = process;\n\t\tconsole.error('');\n\t\tconsole.error('\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E');\n\t\tconsole.error(`\u2502 Update available: ${update.currentVersion} \u2192 ${update.latestVersion.padEnd(37)}\u2502`);\n\t\tconsole.error('\u2502 \u2502');\n\t\tif (update.downloadUrl) {\n\t\t\tconsole.error('\u2502 To update, run one of: \u2502');\n\t\t\tif (platform === 'darwin') {\n\t\t\t\tconsole.error('\u2502 curl -fsSL https://spamscanner.net/install.sh | sh \u2502');\n\t\t\t} else if (platform === 'win32') {\n\t\t\t\tconsole.error('\u2502 irm https://spamscanner.net/install.ps1 | iex \u2502');\n\t\t\t} else {\n\t\t\t\tconsole.error('\u2502 curl -fsSL https://spamscanner.net/install.sh | sh \u2502');\n\t\t\t}\n\n\t\t\tconsole.error('\u2502 \u2502');\n\t\t\tconsole.error('\u2502 Or download manually from: \u2502');\n\t\t} else {\n\t\t\tconsole.error('\u2502 Download from: \u2502');\n\t\t}\n\n\t\tconsole.error('\u2502 https://github.com/spamscanner/spamscanner/releases \u2502');\n\t\tconsole.error('\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F');\n\t\tconsole.error('');\n\t}\n}\n\n/**\n * Format the list of supported languages for help text\n * @returns {string} Formatted language list\n */\nfunction formatLanguageList() {\n\tconst entries = Object.entries(SUPPORTED_LANGUAGES);\n\tconst lines = [];\n\tfor (let i = 0; i < entries.length; i += 4) {\n\t\tconst chunk = entries.slice(i, i + 4);\n\t\tconst formatted = chunk.map(([code, name]) => `${code} (${name})`).join(', ');\n\t\tlines.push(` ${formatted}`);\n\t}\n\n\treturn lines.join('\\n');\n}\n\nconst HELP_TEXT = `\nSpamScanner CLI v${VERSION}\n\nUsage:\n spamscanner <command> [options]\n\nCommands:\n scan <file> Scan an email file for spam\n scan - Scan email from stdin\n server Start TCP server mode\n update Check for updates\n help Show this help message\n version Show version number\n\nGeneral Options:\n -h, --help Show help\n -v, --version Show version\n -j, --json Output results as JSON\n --verbose Show detailed output\n --debug Enable debug mode\n --timeout <ms> Scan timeout in milliseconds (default: 30000)\n --no-update-check Disable automatic update check\n\nSpam Detection Options:\n --threshold <score> Spam score threshold (default: 5.0)\n --check-classifier Include Bayesian classifier in scoring (default: true)\n --check-phishing Include phishing detection in scoring (default: true)\n --check-executables Include executable detection in scoring (default: true)\n --check-macros Include macro detection in scoring (default: true)\n --check-virus Include virus detection in scoring (default: true)\n --check-nsfw Include NSFW detection in scoring (default: false)\n --check-toxicity Include toxicity detection in scoring (default: false)\n --no-classifier Disable Bayesian classifier scoring\n --no-phishing Disable phishing scoring\n --no-executables Disable executable scoring\n --no-macros Disable macro scoring\n --no-virus Disable virus scoring\n\nScore Weights (customize scoring):\n --score-classifier <n> Classifier spam score weight (default: 5.0)\n --score-phishing <n> Phishing score per issue (default: 5.0)\n --score-executable <n> Executable score per file (default: 10.0)\n --score-macro <n> Macro score per detection (default: 5.0)\n --score-virus <n> Virus score per detection (default: 100.0)\n --score-nsfw <n> NSFW score per detection (default: 3.0)\n --score-toxicity <n> Toxicity score per detection (default: 3.0)\n\nScanner Configuration Options:\n --languages <list> Comma-separated list of supported language codes (default: all)\n Use empty string or 'all' for all languages\n --mixed-language Enable mixed language detection in emails\n --no-macro-detection Disable macro detection in attachments\n --no-pattern-recognition Disable advanced pattern recognition\n --strict-idn Enable strict IDN/homograph detection\n --nsfw-threshold <n> NSFW detection threshold 0.0-1.0 (default: 0.6)\n --toxicity-threshold <n> Toxicity detection threshold 0.0-1.0 (default: 0.7)\n --clamscan-path <path> Path to clamscan binary (default: /usr/bin/clamscan)\n --clamdscan-path <path> Path to clamdscan binary (default: /usr/bin/clamdscan)\n\nAuthentication Options (mailauth):\n --enable-auth Enable DKIM/SPF/ARC/DMARC/BIMI authentication\n --sender-ip <ip> Remote IP address of the sender (required for auth)\n --sender-hostname <host> Resolved hostname of the sender (from reverse DNS)\n --helo <hostname> HELO/EHLO hostname\n --sender <email> Envelope sender (MAIL FROM)\n --mta <hostname> MTA hostname for auth headers (default: spamscanner)\n --auth-timeout <ms> DNS lookup timeout for auth (default: 10000)\n\nReputation Options (Forward Email API):\n --enable-reputation Enable Forward Email reputation checking\n --reputation-url <url> Custom reputation API URL\n --reputation-timeout <ms> Reputation API timeout (default: 10000)\n --only-aligned Only check aligned/authenticated attributes for reputation (default: true)\n --no-only-aligned Check all attributes regardless of alignment\n\nHeader Options:\n --add-headers Add X-Spam-* headers to output (for mail server integration)\n --add-auth-headers Add Authentication-Results header to output\n --prepend-subject Prepend [SPAM] to subject if spam detected\n --subject-tag <tag> Custom subject tag (default: [SPAM])\n\nServer Options:\n --port <port> TCP server port (default: 7830)\n --host <host> TCP server host (default: 127.0.0.1)\n\nSupported Languages (use ISO 639-1 codes with --languages):\n${formatLanguageList()}\n\nExamples:\n # Scan a file\n spamscanner scan email.eml\n\n # Scan from stdin (for Postfix integration)\n cat email.eml | spamscanner scan -\n\n # Scan with JSON output\n spamscanner scan email.eml --json\n\n # Scan with custom threshold\n spamscanner scan email.eml --threshold 3.0\n\n # Scan with only classifier and phishing checks\n spamscanner scan email.eml --no-executables --no-macros --no-virus\n\n # Scan and add spam headers (for mail server integration)\n spamscanner scan email.eml --add-headers --prepend-subject\n\n # Scan with specific language support\n spamscanner scan email.eml --languages en,es,fr\n\n # Scan with mixed language detection\n spamscanner scan email.eml --mixed-language\n\n # Start TCP server\n spamscanner server --port 7830\n\n # Scan with authentication (DKIM/SPF/DMARC)\n spamscanner scan email.eml --enable-auth --sender-ip 192.168.1.1 --sender user@example.com\n\n # Scan with reputation checking\n spamscanner scan email.eml --enable-reputation\n\n # Full mail server integration\n spamscanner scan email.eml --enable-auth --enable-reputation --sender-ip 192.168.1.1 --add-headers --add-auth-headers\n\n # Check for updates\n spamscanner update\n\nExit Codes:\n 0 - Clean (not spam)\n 1 - Spam detected\n 2 - Error occurred\n\nX-Spam Headers (when --add-headers is used):\n X-Spam-Status: Yes/No, score=X.X required=Y.Y tests=TEST1,TEST2,...\n X-Spam-Score: X.X\n X-Spam-Flag: YES/NO\n X-Spam-Tests: Comma-separated list of triggered tests\n`;\n\n/**\n * Parse command line arguments\n * @param {string[]} args - Command line arguments\n * @returns {object} Parsed arguments\n */\nfunction parseArgs(args) {\n\tconst result = {\n\t\tcommand: null,\n\t\tfile: null,\n\t\tjson: false,\n\t\tverbose: false,\n\t\tdebug: false,\n\t\tport: 7830,\n\t\thost: '127.0.0.1',\n\t\ttimeout: 30_000,\n\t\thelp: false,\n\t\tversion: false,\n\t\tnoUpdateCheck: false,\n\t\t// Spam detection options\n\t\tthreshold: 5,\n\t\tcheckClassifier: true,\n\t\tcheckPhishing: true,\n\t\tcheckExecutables: true,\n\t\tcheckMacros: true,\n\t\tcheckVirus: true,\n\t\tcheckNsfw: false,\n\t\tcheckToxicity: false,\n\t\t// Score weights\n\t\tscores: {...DEFAULT_SCORES},\n\t\t// Header options\n\t\taddHeaders: false,\n\t\tprependSubject: false,\n\t\tsubjectTag: '[SPAM]',\n\t\t// Scanner configuration options\n\t\tsupportedLanguages: [], // Empty = all languages\n\t\tenableMixedLanguageDetection: false,\n\t\tenableMacroDetection: true,\n\t\tenableAdvancedPatternRecognition: true,\n\t\tstrictIdnDetection: false,\n\t\tnsfwThreshold: 0.6,\n\t\ttoxicityThreshold: 0.7,\n\t\tclamscanPath: '/usr/bin/clamscan',\n\t\tclamdscanPath: '/usr/bin/clamdscan',\n\t\t// Authentication options\n\t\tenableAuth: false,\n\t\tsenderIp: null,\n\t\tsenderHostname: null,\n\t\thelo: null,\n\t\tsender: null,\n\t\tmta: 'spamscanner',\n\t\tauthTimeout: 10_000,\n\t\t// Reputation options\n\t\tenableReputation: false,\n\t\treputationUrl: 'https://api.forwardemail.net/v1/reputation',\n\t\treputationTimeout: 10_000,\n\t\tonlyAligned: true,\n\t\t// Additional header options\n\t\taddAuthHeaders: false,\n\t};\n\n\tfor (let index = 0; index < args.length; index++) {\n\t\tconst arg = args[index];\n\n\t\tswitch (arg) {\n\t\t\tcase 'scan':\n\t\t\tcase 'server':\n\t\t\tcase 'help':\n\t\t\tcase 'version':\n\t\t\tcase 'update': {\n\t\t\t\tresult.command = arg;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '-h':\n\t\t\tcase '--help': {\n\t\t\t\tresult.help = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '-v':\n\t\t\tcase '--version': {\n\t\t\t\tresult.version = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '-j':\n\t\t\tcase '--json': {\n\t\t\t\tresult.json = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--verbose': {\n\t\t\t\tresult.verbose = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--debug': {\n\t\t\t\tresult.debug = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-update-check': {\n\t\t\t\tresult.noUpdateCheck = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--port': {\n\t\t\t\tresult.port = Number.parseInt(args[++index], 10);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--host': {\n\t\t\t\tresult.host = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--timeout': {\n\t\t\t\tresult.timeout = Number.parseInt(args[++index], 10);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Spam detection options\n\t\t\tcase '--threshold': {\n\t\t\t\tresult.threshold = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-classifier': {\n\t\t\t\tresult.checkClassifier = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-phishing': {\n\t\t\t\tresult.checkPhishing = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-executables': {\n\t\t\t\tresult.checkExecutables = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-macros': {\n\t\t\t\tresult.checkMacros = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-virus': {\n\t\t\t\tresult.checkVirus = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-nsfw': {\n\t\t\t\tresult.checkNsfw = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--check-toxicity': {\n\t\t\t\tresult.checkToxicity = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-classifier': {\n\t\t\t\tresult.checkClassifier = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-phishing': {\n\t\t\t\tresult.checkPhishing = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-executables': {\n\t\t\t\tresult.checkExecutables = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-macros': {\n\t\t\t\tresult.checkMacros = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-virus': {\n\t\t\t\tresult.checkVirus = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Score weights\n\t\t\tcase '--score-classifier': {\n\t\t\t\tresult.scores.classifier = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-phishing': {\n\t\t\t\tresult.scores.phishing = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-executable': {\n\t\t\t\tresult.scores.executable = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-macro': {\n\t\t\t\tresult.scores.macro = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-virus': {\n\t\t\t\tresult.scores.virus = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-nsfw': {\n\t\t\t\tresult.scores.nsfw = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--score-toxicity': {\n\t\t\t\tresult.scores.toxicity = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Header options\n\t\t\tcase '--add-headers': {\n\t\t\t\tresult.addHeaders = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--prepend-subject': {\n\t\t\t\tresult.prependSubject = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--subject-tag': {\n\t\t\t\tresult.subjectTag = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Scanner configuration options\n\t\t\tcase '--languages': {\n\t\t\t\tconst langArg = args[++index];\n\t\t\t\tresult.supportedLanguages = langArg && langArg !== 'all' && langArg !== '' ? langArg.split(',').map(l => l.trim().toLowerCase()) : [];\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--mixed-language': {\n\t\t\t\tresult.enableMixedLanguageDetection = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-macro-detection': {\n\t\t\t\tresult.enableMacroDetection = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-pattern-recognition': {\n\t\t\t\tresult.enableAdvancedPatternRecognition = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--strict-idn': {\n\t\t\t\tresult.strictIdnDetection = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--nsfw-threshold': {\n\t\t\t\tresult.nsfwThreshold = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--toxicity-threshold': {\n\t\t\t\tresult.toxicityThreshold = Number.parseFloat(args[++index]);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--clamscan-path': {\n\t\t\t\tresult.clamscanPath = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--clamdscan-path': {\n\t\t\t\tresult.clamdscanPath = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Authentication options\n\t\t\tcase '--enable-auth': {\n\t\t\t\tresult.enableAuth = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--sender-ip': {\n\t\t\t\tresult.senderIp = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--sender-hostname': {\n\t\t\t\tresult.senderHostname = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--helo': {\n\t\t\t\tresult.helo = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--sender': {\n\t\t\t\tresult.sender = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--mta': {\n\t\t\t\tresult.mta = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--auth-timeout': {\n\t\t\t\tresult.authTimeout = Number.parseInt(args[++index], 10);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Reputation options\n\t\t\tcase '--enable-reputation': {\n\t\t\t\tresult.enableReputation = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--reputation-url': {\n\t\t\t\tresult.reputationUrl = args[++index];\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--reputation-timeout': {\n\t\t\t\tresult.reputationTimeout = Number.parseInt(args[++index], 10);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--only-aligned': {\n\t\t\t\tresult.onlyAligned = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase '--no-only-aligned': {\n\t\t\t\tresult.onlyAligned = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Additional header options\n\t\t\tcase '--add-auth-headers': {\n\t\t\t\tresult.addAuthHeaders = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tif (!result.file && result.command === 'scan'\n\t\t\t\t\t&& (arg === '-' || !arg.startsWith('-'))) {\n\t\t\t\t\tresult.file = arg;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Read email content from file or stdin\n * @param {string} file - File path or '-' for stdin\n * @returns {Promise<Buffer>} Email content\n */\nasync function readEmail(file) {\n\tif (file === '-') {\n\t\t// Read from stdin\n\t\tconst chunks = [];\n\t\tfor await (const chunk of process.stdin) {\n\t\t\tchunks.push(chunk);\n\t\t}\n\n\t\treturn Buffer.concat(chunks);\n\t}\n\n\t// Read from file\n\tconst chunks = [];\n\tconst stream = createReadStream(file);\n\tfor await (const chunk of stream) {\n\t\tchunks.push(chunk);\n\t}\n\n\treturn Buffer.concat(chunks);\n}\n\n/**\n * Calculate spam score based on scan results and options\n * @param {object} result - Scan result from SpamScanner\n * @param {object} options - CLI options\n * @returns {object} Score details\n */\nfunction calculateScore(result, options) {\n\tconst {scores} = options;\n\tconst tests = [];\n\tlet totalScore = 0;\n\n\t// Classifier score\n\tif (options.checkClassifier && result.results?.classification) {\n\t\tconst {category, probability} = result.results.classification;\n\t\tif (category === 'spam') {\n\t\t\t// Scale score by probability (0.5-1.0 maps to 0-full score)\n\t\t\tconst scaledScore = scores.classifier * Math.max(0, (probability - 0.5) * 2);\n\t\t\ttotalScore += scaledScore;\n\t\t\ttests.push(`BAYES_SPAM(${scaledScore.toFixed(1)})`);\n\t\t} else if (category === 'ham' && probability > 0.8) {\n\t\t\t// Give negative score for confident ham\n\t\t\tconst hamBonus = -1 * (probability - 0.8) * 5;\n\t\t\ttotalScore += hamBonus;\n\t\t\ttests.push(`BAYES_HAM(${hamBonus.toFixed(1)})`);\n\t\t}\n\t}\n\n\t// Phishing score\n\tif (options.checkPhishing && result.results?.phishing?.length > 0) {\n\t\tconst phishingScore = result.results.phishing.length * scores.phishing;\n\t\ttotalScore += phishingScore;\n\t\ttests.push(`PHISHING_DETECTED(${phishingScore.toFixed(1)})`);\n\t}\n\n\t// Executable score\n\tif (options.checkExecutables && result.results?.executables?.length > 0) {\n\t\tconst execScore = result.results.executables.length * scores.executable;\n\t\ttotalScore += execScore;\n\t\ttests.push(`EXECUTABLE_ATTACHMENT(${execScore.toFixed(1)})`);\n\t}\n\n\t// Macro score\n\tif (options.checkMacros && result.results?.macros?.length > 0) {\n\t\tconst macroScore = result.results.macros.length * scores.macro;\n\t\ttotalScore += macroScore;\n\t\ttests.push(`MACRO_DETECTED(${macroScore.toFixed(1)})`);\n\t}\n\n\t// Virus score\n\tif (options.checkVirus && result.results?.viruses?.length > 0) {\n\t\tconst virusScore = result.results.viruses.length * scores.virus;\n\t\ttotalScore += virusScore;\n\t\ttests.push(`VIRUS_DETECTED(${virusScore.toFixed(1)})`);\n\t}\n\n\t// NSFW score\n\tif (options.checkNsfw && result.results?.nsfw?.length > 0) {\n\t\tconst nsfwScore = result.results.nsfw.length * scores.nsfw;\n\t\ttotalScore += nsfwScore;\n\t\ttests.push(`NSFW_CONTENT(${nsfwScore.toFixed(1)})`);\n\t}\n\n\t// Toxicity score\n\tif (options.checkToxicity && result.results?.toxicity?.length > 0) {\n\t\tconst toxicScore = result.results.toxicity.length * scores.toxicity;\n\t\ttotalScore += toxicScore;\n\t\ttests.push(`TOXIC_CONTENT(${toxicScore.toFixed(1)})`);\n\t}\n\n\t// Authentication score (from mailauth)\n\tif (result.results?.authentication?.score) {\n\t\tconst authScore = result.results.authentication.score;\n\t\ttotalScore += authScore.score;\n\t\ttests.push(...authScore.tests);\n\t}\n\n\t// Reputation score\n\tif (result.results?.reputation) {\n\t\tconst rep = result.results.reputation;\n\t\tif (rep.isDenylisted) {\n\t\t\ttotalScore += 10;\n\t\t\ttests.push('DENYLISTED(10.0)');\n\t\t}\n\n\t\tif (rep.isTruthSource) {\n\t\t\ttotalScore -= 5;\n\t\t\ttests.push('TRUTH_SOURCE(-5.0)');\n\t\t} else if (rep.isAllowlisted) {\n\t\t\ttotalScore -= 3;\n\t\t\ttests.push('ALLOWLISTED(-3.0)');\n\t\t}\n\t}\n\n\tlet isSpam = totalScore >= options.threshold;\n\n\t// Override spam status based on reputation\n\tif (result.results?.reputation) {\n\t\tconst rep = result.results.reputation;\n\t\tif (rep.isDenylisted) {\n\t\t\tisSpam = true;\n\t\t} else if ((rep.isTruthSource || rep.isAllowlisted) && !result.results?.viruses?.length && !result.results?.executables?.length) {\n\t\t\tisSpam = false;\n\t\t}\n\t}\n\n\treturn {\n\t\tscore: totalScore,\n\t\tthreshold: options.threshold,\n\t\tisSpam,\n\t\ttests,\n\t};\n}\n\n/**\n * Generate X-Spam headers based on scan results\n * @param {object} scoreDetails - Score calculation details\n * @returns {object} Headers object\n */\nfunction generateSpamHeaders(scoreDetails) {\n\tconst {score, threshold, isSpam, tests} = scoreDetails;\n\tconst status = isSpam ? 'Yes' : 'No';\n\tconst flag = isSpam ? 'YES' : 'NO';\n\n\treturn {\n\t\t'X-Spam-Status': `${status}, score=${score.toFixed(1)} required=${threshold.toFixed(1)} tests=${tests.join(',')} version=${VERSION}`,\n\t\t'X-Spam-Score': score.toFixed(1),\n\t\t'X-Spam-Flag': flag,\n\t\t'X-Spam-Tests': tests.join(', '),\n\t};\n}\n\n/**\n * Modify email content with spam headers and subject tag\n * @param {Buffer} emailContent - Original email content\n * @param {object} options - CLI options\n * @param {object} scoreDetails - Score calculation details\n * @returns {string} Modified email content\n */\nfunction modifyEmail(emailContent, options, scoreDetails, authResultsHeader = null) {\n\tconst emailString = emailContent.toString('utf8');\n\tconst headers = generateSpamHeaders(scoreDetails);\n\n\t// Add Authentication-Results header if available\n\tif (options.addAuthHeaders && authResultsHeader) {\n\t\theaders['Authentication-Results'] = authResultsHeader;\n\t}\n\n\t// Find the header/body boundary\n\tconst headerEndMatch = emailString.match(/\\r?\\n\\r?\\n/);\n\tif (!headerEndMatch) {\n\t\t// No body, just append headers\n\t\treturn emailString + '\\r\\n' + Object.entries(headers)\n\t\t\t.map(([key, value]) => `${key}: ${value}`)\n\t\t\t.join('\\r\\n');\n\t}\n\n\tconst headerEndIndex = headerEndMatch.index;\n\tconst lineEnding = headerEndMatch[0].startsWith('\\r\\n') ? '\\r\\n' : '\\n';\n\tconst headerPart = emailString.slice(0, headerEndIndex);\n\tconst bodyPart = emailString.slice(headerEndIndex);\n\n\t// Add X-Spam headers\n\tlet newHeaders = headerPart;\n\tif (options.addHeaders) {\n\t\tconst headerLines = Object.entries(headers)\n\t\t\t.map(([key, value]) => `${key}: ${value}`)\n\t\t\t.join(lineEnding);\n\t\tnewHeaders = headerPart + lineEnding + headerLines;\n\t}\n\n\t// Prepend subject tag if spam\n\tif (options.prependSubject && scoreDetails.isSpam) {\n\t\tconst subjectMatch = newHeaders.match(/^(subject:\\s*)(.*)$/im);\n\t\tif (subjectMatch) {\n\t\t\tconst [fullMatch, prefix, subject] = subjectMatch;\n\t\t\t// Only prepend if not already tagged\n\t\t\tif (!subject.startsWith(options.subjectTag)) {\n\t\t\t\tconst newSubject = `${prefix}${options.subjectTag} ${subject}`;\n\t\t\t\tnewHeaders = newHeaders.replace(fullMatch, newSubject);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newHeaders + bodyPart;\n}\n\n/**\n * Format scan results for human-readable output\n * @param {object} result - Scan result\n * @param {object} scoreDetails - Score calculation details\n * @param {boolean} verbose - Show verbose output\n * @returns {string} Formatted output\n */\nfunction formatResult(result, scoreDetails, verbose) {\n\tconst lines = [];\n\tconst {score, threshold, isSpam, tests} = scoreDetails;\n\n\tif (isSpam) {\n\t\tlines.push(`SPAM DETECTED (score: ${score.toFixed(1)}, threshold: ${threshold.toFixed(1)})`);\n\t} else {\n\t\tlines.push(`Clean (score: ${score.toFixed(1)}, threshold: ${threshold.toFixed(1)})`);\n\t}\n\n\tif (tests.length > 0) {\n\t\tlines.push(`Tests: ${tests.join(', ')}`);\n\t}\n\n\tif (verbose) {\n\t\tlines.push('', 'Details:');\n\n\t\tif (result.results?.classification) {\n\t\t\tconst prob = (result.results.classification.probability * 100).toFixed(1);\n\t\t\tlines.push(` Classification: ${result.results.classification.category} (${prob}%)`);\n\t\t}\n\n\t\tif (result.results?.phishing?.length > 0) {\n\t\t\tlines.push(` Phishing: ${result.results.phishing.length} issue(s) detected`);\n\t\t\tfor (const issue of result.results.phishing) {\n\t\t\t\tlines.push(` - ${issue.type}: ${issue.description || issue.message || 'N/A'}`);\n\t\t\t}\n\t\t}\n\n\t\tif (result.results?.executables?.length > 0) {\n\t\t\tlines.push(` Executables: ${result.results.executables.length} dangerous file(s) detected`);\n\t\t\tfor (const exec of result.results.executables) {\n\t\t\t\tlines.push(` - ${exec.filename || exec.extension || 'Unknown'}`);\n\t\t\t}\n\t\t}\n\n\t\tif (result.results?.viruses?.length > 0) {\n\t\t\tlines.push(` Viruses: ${result.results.viruses.length} virus(es) detected`);\n\t\t\tfor (const virus of result.results.viruses) {\n\t\t\t\tlines.push(` - ${virus.name || virus.message || 'Unknown'}`);\n\t\t\t}\n\t\t}\n\n\t\tif (result.results?.macros?.length > 0) {\n\t\t\tlines.push(` Macros: ${result.results.macros.length} macro(s) detected`);\n\t\t}\n\n\t\tif (result.results?.toxicity?.length > 0) {\n\t\t\tlines.push(` Toxicity: ${result.results.toxicity.length} toxic content detected`);\n\t\t}\n\n\t\tif (result.results?.nsfw?.length > 0) {\n\t\t\tlines.push(` NSFW: ${result.results.nsfw.length} NSFW content detected`);\n\t\t}\n\n\t\t// Authentication results\n\t\tif (result.results?.authentication) {\n\t\t\tconst auth = result.results.authentication;\n\t\t\tlines.push('', ' Authentication:');\n\t\t\tif (auth.dkim?.status?.result) {\n\t\t\t\tlines.push(` DKIM: ${auth.dkim.status.result}`);\n\t\t\t}\n\n\t\t\tif (auth.spf?.status?.result) {\n\t\t\t\tlines.push(` SPF: ${auth.spf.status.result}`);\n\t\t\t}\n\n\t\t\tif (auth.dmarc?.status?.result) {\n\t\t\t\tlines.push(` DMARC: ${auth.dmarc.status.result}`);\n\t\t\t}\n\n\t\t\tif (auth.arc?.status?.result) {\n\t\t\t\tlines.push(` ARC: ${auth.arc.status.result}`);\n\t\t\t}\n\t\t}\n\n\t\t// Reputation results\n\t\tif (result.results?.reputation) {\n\t\t\tconst rep = result.results.reputation;\n\t\t\tlines.push('', ' Reputation:');\n\t\t\tif (rep.isTruthSource) {\n\t\t\t\tlines.push(' Status: Truth Source');\n\t\t\t} else if (rep.isAllowlisted) {\n\t\t\t\tlines.push(` Status: Allowlisted (${rep.allowlistValue || 'N/A'})`);\n\t\t\t} else if (rep.isDenylisted) {\n\t\t\t\tlines.push(` Status: DENYLISTED (${rep.denylistValue || 'N/A'})`);\n\t\t\t} else {\n\t\t\t\tlines.push(' Status: Unknown');\n\t\t\t}\n\n\t\t\tif (rep.checkedValues?.length > 0) {\n\t\t\t\tlines.push(` Checked: ${rep.checkedValues.join(', ')}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn lines.join('\\n');\n}\n\n/**\n * Build SpamScanner configuration from CLI options\n * @param {object} options - CLI options\n * @returns {object} SpamScanner configuration\n */\nfunction buildScannerConfig(options) {\n\treturn {\n\t\tdebug: options.debug,\n\t\ttimeout: options.timeout,\n\t\tsupportedLanguages: options.supportedLanguages,\n\t\tenableMixedLanguageDetection: options.enableMixedLanguageDetection,\n\t\tenableMacroDetection: options.enableMacroDetection,\n\t\tenableAdvancedPatternRecognition: options.enableAdvancedPatternRecognition,\n\t\tstrictIDNDetection: options.strictIdnDetection,\n\t\tnsfwThreshold: options.nsfwThreshold,\n\t\ttoxicityThreshold: options.toxicityThreshold,\n\t\tclamscan: {\n\t\t\tclamscanPath: options.clamscanPath,\n\t\t\tclamdscanPath: options.clamdscanPath,\n\t\t},\n\t\t// Authentication options\n\t\tenableAuthentication: options.enableAuth,\n\t\tauthOptions: {\n\t\t\tip: options.senderIp,\n\t\t\thostname: options.senderHostname,\n\t\t\thelo: options.helo,\n\t\t\tmta: options.mta,\n\t\t\tsender: options.sender,\n\t\t\ttimeout: options.authTimeout,\n\t\t},\n\t\t// Reputation options\n\t\tenableReputation: options.enableReputation,\n\t\treputationOptions: {\n\t\t\tapiUrl: options.reputationUrl,\n\t\t\ttimeout: options.reputationTimeout,\n\t\t\tonlyAligned: options.onlyAligned,\n\t\t},\n\t};\n}\n\n/**\n * Scan an email and output results\n * @param {object} options - Scan options\n */\nasync function scanCommand(options) {\n\tconst {file, json, verbose, addHeaders, prependSubject} = options;\n\n\tif (!file) {\n\t\tconsole.error('Error: No file specified. Use \"spamscanner scan <file>\" or \"spamscanner scan -\" for stdin.');\n\t\tprocess.exit(2);\n\t}\n\n\ttry {\n\t\t// Check if file exists (unless reading from stdin)\n\t\tif (file !== '-') {\n\t\t\ttry {\n\t\t\t\treadFileSync(file);\n\t\t\t} catch {\n\t\t\t\tconsole.error(`Error: File not found: ${file}`);\n\t\t\t\tprocess.exit(2);\n\t\t\t}\n\t\t}\n\n\t\tconst scannerConfig = buildScannerConfig(options);\n\t\tconst scanner = new SpamScanner(scannerConfig);\n\n\t\tconst emailContent = await readEmail(file);\n\t\tconst result = await scanner.scan(emailContent);\n\n\t\t// Calculate score based on options\n\t\tconst scoreDetails = calculateScore(result, options);\n\n\t\t// Generate output\n\t\tconst output = {\n\t\t\tisSpam: scoreDetails.isSpam,\n\t\t\tscore: scoreDetails.score,\n\t\t\tthreshold: scoreDetails.threshold,\n\t\t\ttests: scoreDetails.tests,\n\t\t\tmessage: result.message,\n\t\t\tresults: result.results,\n\t\t\tlinks: result.links,\n\t\t\ttokens: result.tokens,\n\t\t\tmail: result.mail,\n\t\t};\n\n\t\t// Add headers if requested\n\t\tif (addHeaders || prependSubject || options.addAuthHeaders) {\n\t\t\toutput.headers = generateSpamHeaders(scoreDetails);\n\t\t\tconst authResultsHeader = result.results?.authentication?.authResultsHeader || null;\n\t\t\toutput.modifiedEmail = modifyEmail(emailContent, options, scoreDetails, authResultsHeader);\n\t\t}\n\n\t\tif (json) {\n\t\t\tconsole.log(JSON.stringify(output, null, 2));\n\t\t} else if (addHeaders || prependSubject || options.addAuthHeaders) {\n\t\t\t// Output modified email for piping to mail server\n\t\t\tconsole.log(output.modifiedEmail);\n\t\t} else {\n\t\t\tconsole.log(formatResult(result, scoreDetails, verbose));\n\t\t}\n\n\t\tprocess.exit(scoreDetails.isSpam ? 1 : 0);\n\t} catch (error) {\n\t\tconsole.error(`Error scanning email: ${error.message}`);\n\t\tif (options.debug) {\n\t\t\tconsole.error(error.stack);\n\t\t}\n\n\t\tprocess.exit(2);\n\t}\n}\n\n/**\n * Start TCP server for high-volume scanning\n * @param {object} options - Server options\n */\nasync function serverCommand(options) {\n\tconst {port, host, json, verbose, debug} = options;\n\n\tconst scannerConfig = buildScannerConfig(options);\n\tconst scanner = new SpamScanner(scannerConfig);\n\n\tconst server = createServer(socket => {\n\t\tconst chunks = [];\n\n\t\tsocket.on('data', chunk => {\n\t\t\tchunks.push(chunk);\n\t\t});\n\n\t\tsocket.on('end', async () => {\n\t\t\ttry {\n\t\t\t\tconst emailContent = Buffer.concat(chunks);\n\t\t\t\tconst result = await scanner.scan(emailContent);\n\t\t\t\tconst scoreDetails = calculateScore(result, options);\n\n\t\t\t\tconst output = {\n\t\t\t\t\tisSpam: scoreDetails.isSpam,\n\t\t\t\t\tscore: scoreDetails.score,\n\t\t\t\t\tthreshold: scoreDetails.threshold,\n\t\t\t\t\ttests: scoreDetails.tests,\n\t\t\t\t\tmessage: result.message,\n\t\t\t\t};\n\n\t\t\t\tif (options.addHeaders) {\n\t\t\t\t\toutput.headers = generateSpamHeaders(scoreDetails);\n\t\t\t\t}\n\n\t\t\t\tif (json) {\n\t\t\t\t\tsocket.write(JSON.stringify(output));\n\t\t\t\t} else {\n\t\t\t\t\tsocket.write(formatResult(result, scoreDetails, verbose));\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconst errorResponse = json\n\t\t\t\t\t? JSON.stringify({error: error.message})\n\t\t\t\t\t: `Error: ${error.message}`;\n\t\t\t\tsocket.write(errorResponse);\n\t\t\t\tif (debug) {\n\t\t\t\t\tconsole.error(error.stack);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsocket.end();\n\t\t});\n\n\t\tsocket.on('error', error => {\n\t\t\tconsole.error(`Socket error: ${error.message}`);\n\t\t});\n\t});\n\n\tserver.listen(port, host, () => {\n\t\tconsole.log(`SpamScanner TCP server listening on ${host}:${port}`);\n\t\tconsole.log('Send email content to scan, close connection to receive results.');\n\t\tconsole.log('Press Ctrl+C to stop.');\n\t});\n\n\tserver.on('error', error => {\n\t\tconsole.error(`Server error: ${error.message}`);\n\t\tprocess.exit(2);\n\t});\n}\n\n/**\n * Main entry point\n */\nasync function main() {\n\tconst args = process.argv.slice(2);\n\tconst options = parseArgs(args);\n\n\t// Handle help and version flags first\n\tif (options.help) {\n\t\tconsole.log(HELP_TEXT);\n\t\tprocess.exit(0);\n\t}\n\n\tif (options.version) {\n\t\tconsole.log(`SpamScanner v${VERSION}`);\n\t\tprocess.exit(0);\n\t}\n\n\t// Check for updates (unless disabled)\n\tif (!options.noUpdateCheck && options.command !== 'update') {\n\t\t// Run update check in background, don't block\n\t\t// eslint-disable-next-line promise/prefer-await-to-then, no-void\n\t\tvoid printUpdateNotification().catch(() => {\n\t\t\t// Ignore errors\n\t\t});\n\t}\n\n\t// Handle commands\n\tswitch (options.command) {\n\t\tcase 'scan': {\n\t\t\tawait scanCommand(options);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'server': {\n\t\t\tawait serverCommand(options);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'update': {\n\t\t\tconsole.log(`SpamScanner v${VERSION}`);\n\t\t\tconsole.log('Checking for updates...');\n\t\t\tconst update = await checkForUpdates(true);\n\t\t\tif (update) {\n\t\t\t\tconsole.log(`New version available: ${update.latestVersion}`);\n\t\t\t\tconsole.log(`Download from: ${update.releaseUrl}`);\n\t\t\t\tif (update.downloadUrl) {\n\t\t\t\t\tconsole.log(`Direct download: ${update.downloadUrl}`);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconsole.log('You are running the latest version.');\n\t\t\t}\n\n\t\t\tprocess.exit(0);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'help': {\n\t\t\tconsole.log(HELP_TEXT);\n\t\t\tprocess.exit(0);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'version': {\n\t\t\tconsole.log(`SpamScanner v${VERSION}`);\n\t\t\tprocess.exit(0);\n\t\t\tbreak;\n\t\t}\n\n\t\tdefault: {\n\t\t\tconsole.error('Unknown command. Use \"spamscanner help\" for usage information.');\n\t\t\tprocess.exit(2);\n\t\t}\n\t}\n}\n\nmain().catch(error => {\n\tconsole.error(`Fatal error: ${error.message}`);\n\tprocess.exit(2);\n});\n", "import {Buffer} from 'node:buffer';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport process from 'node:process';\nimport {createHash} from 'node:crypto';\nimport {debuglog} from 'node:util';\nimport {fileURLToPath} from 'node:url';\nimport autoBind from 'auto-bind';\nimport AFHConvert from 'ascii-fullwidth-halfwidth-convert';\nimport ClamScan from 'clamscan';\nimport NaiveBayes from '@ladjs/naivebayes';\nimport arrayJoinConjunction from 'array-join-conjunction';\nimport bitcoinRegex from 'bitcoin-regex';\nimport creditCardRegex from 'credit-card-regex';\nimport emailRegexSafe from 'email-regex-safe';\nimport escapeStringRegexp from 'escape-string-regexp';\nimport expandContractions from '@stdlib/nlp-expand-contractions';\nimport fileExtension from 'file-extension';\nimport floatingPointRegex from 'floating-point-regex';\nimport lande from 'lande'; // Replaced franc with lande as per TODO\nimport hexaColorRegex from 'hexa-color-regex';\nimport {parse as parseTldts} from 'tldts';\nimport ipRegex from 'ip-regex';\nimport isBuffer from 'is-buffer';\nimport isSANB from 'is-string-and-not-blank';\nimport macRegex from 'mac-regex';\nimport natural from 'natural';\nimport normalizeUrl from 'normalize-url';\nimport phoneRegex from 'phone-regex';\nimport snowball from 'node-snowball';\nimport striptags from 'striptags';\nimport superagent from 'superagent';\nimport sw from 'stopword';\nimport urlRegexSafe from 'url-regex-safe';\nimport {simpleParser} from 'mailparser';\nimport {fileTypeFromBuffer} from 'file-type';\n// SpamScanner modules\nimport {authenticate, calculateAuthScore, formatAuthResultsHeader} from './auth.js';\nimport {checkReputationBatch, aggregateReputationResults} from './reputation.js';\nimport {isArbitrary, buildSessionInfo} from './is-arbitrary.js';\nimport {extractAttributes} from './get-attributes.js';\n\n// ES module compatibility - handle both ESM and CJS builds\n// In ESM, import.meta.url is defined; in CJS (via esbuild), it's undefined\nconst __filename = import.meta.url ? fileURLToPath(import.meta.url) : '';\nconst __dirname = __filename ? path.dirname(__filename) : process.cwd();\n\n// Find package root - works from both src/ and dist/esm/ or dist/cjs/\nconst findPackageRoot = startDir => {\n\tlet dir = startDir;\n\twhile (dir !== path.dirname(dir)) {\n\t\tif (fs.existsSync(path.join(dir, 'package.json'))) {\n\t\t\treturn dir;\n\t\t}\n\n\t\tdir = path.dirname(dir);\n\t}\n\n\treturn startDir;\n};\n\nconst packageRoot = findPackageRoot(__dirname);\n\n// Load JSON data\nconst executablesData = JSON.parse(fs.readFileSync(path.join(packageRoot, 'executables.json'), 'utf8'));\n\nconst EXECUTABLES = new Set(executablesData);\n\n// Dynamic imports for modules that need to be loaded conditionally\nconst getReplacements = async () => {\n\tconst {default: replacements} = await import('../replacements.js');\n\treturn replacements;\n};\n\nconst getClassifier = async () => {\n\tconst {default: classifier} = await import('../get-classifier.js');\n\treturn classifier;\n};\n\nconst debug = debuglog('spamscanner');\n\n// All tokenizers combined - improved regex pattern\nconst GENERIC_TOKENIZER\n = /[^a-z\u00E1-\u00FA\u00C1-\u00DA\u00E0-\u00FA\u00C0-\u00DA\u00F1\u00FC\\d\u0430-\u044F\u0451\u00E6\u00F8\u00E5\u00E0\u00E1\u1EA3\u00E3\u1EA1\u0103\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u00E2\u1EA5\u1EA7\u1EA9\u1EAB\u1EAD\u00E9\u00E8\u1EBB\u1EBD\u1EB9\u00EA\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u00ED\u00EC\u1EC9\u0129\u1ECB\u00F3\u00F2\u1ECF\u00F5\u1ECD\u00F4\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u01A1\u1EDB\u1EDD\u1EDF\u1EE1\u1EE3\u00FA\u00F9\u1EE7\u0169\u1EE5\u01B0\u1EE9\u1EEB\u1EED\u1EEF\u1EF1\u00FD\u1EF3\u1EF7\u1EF9\u1EF5\u0111\u00E4\u00F6\u00EB\u00EF\u00EE\u00FB\u0153\u00E7\u0105\u017C\u015B\u017A\u0119\u0107\u0144\u0142-]+/i;\n\nconst converter = new AFHConvert();\n\n// Chinese tokenizer setup with proper path resolution\nconst chineseTokenizer = {tokenize: text => text.split(/\\s+/)};\n\n// Enhanced stopwords with fallback for missing language-specific stopwords\nconst stopwordsMap = new Map([\n\t['ar', new Set([...(natural.stopwords || []), ...(sw.ar || [])])],\n\t['bg', new Set([...(natural.stopwords || []), ...(sw.bg || [])])],\n\t['bn', new Set([...(natural.stopwords || []), ...(sw.bn || [])])],\n\t['ca', new Set([...(natural.stopwords || []), ...(sw.ca || [])])],\n\t['cs', new Set([...(natural.stopwords || []), ...(sw.cs || [])])],\n\t['da', new Set([...(natural.stopwords || []), ...(sw.da || [])])],\n\t['de', new Set([...(natural.stopwords || []), ...(sw.de || [])])],\n\t['el', new Set([...(natural.stopwords || []), ...(sw.el || [])])],\n\t['en', new Set([...(natural.stopwords || []), ...(sw.en || [])])],\n\t['es', new Set([...(natural.stopwords || []), ...(sw.es || [])])],\n\t['fa', new Set([...(natural.stopwords || []), ...(sw.fa || [])])],\n\t['fi', new Set([...(natural.stopwords || []), ...(sw.fi || [])])],\n\t['fr', new Set([...(natural.stopwords || []), ...(sw.fr || [])])],\n\t['ga', new Set([...(natural.stopwords || []), ...(sw.ga || [])])],\n\t['gl', new Set([...(natural.stopwords || []), ...(sw.gl || [])])],\n\t['gu', new Set([...(natural.stopwords || []), ...(sw.gu || [])])],\n\t['he', new Set([...(natural.stopwords || []), ...(sw.he || [])])],\n\t['hi', new Set([...(natural.stopwords || []), ...(sw.hi || [])])],\n\t['hr', new Set([...(natural.stopwords || []), ...(sw.hr || [])])],\n\t['hu', new Set([...(natural.stopwords || []), ...(sw.hu || [])])],\n\t['hy', new Set([...(natural.stopwords || []), ...(sw.hy || [])])],\n\t['it', new Set([...(natural.stopwords || []), ...(sw.it || [])])],\n\t['ja', new Set([...(natural.stopwords || []), ...(sw.ja || [])])],\n\t['ko', new Set([...(natural.stopwords || []), ...(sw.ko || [])])],\n\t['la', new Set([...(natural.stopwords || []), ...(sw.la || [])])],\n\t['lt', new Set([...(natural.stopwords || []), ...(sw.lt || [])])],\n\t['lv', new Set([...(natural.stopwords || []), ...(sw.lv || [])])],\n\t['mr', new Set([...(natural.stopwords || []), ...(sw.mr || [])])],\n\t['nl', new Set([...(natural.stopwords || []), ...(sw.nl || [])])],\n\t['no', new Set([...(natural.stopwords || []), ...(sw.nob || [])])],\n\t['pl', new Set([...(natural.stopwords || []), ...(sw.pl || [])])],\n\t['pt', new Set([...(natural.stopwords || []), ...(sw.pt || [])])],\n\t['ro', new Set([...(natural.stopwords || []), ...(sw.ro || [])])],\n\t['ru', new Set([...(natural.stopwords || []), ...(sw.ru || [])])],\n\t['sk', new Set([...(natural.stopwords || []), ...(sw.sk || [])])],\n\t['sl', new Set([...(natural.stopwords || []), ...(sw.sl || [])])],\n\t['sv', new Set([...(natural.stopwords || []), ...(sw.sv || [])])],\n\t['th', new Set([...(natural.stopwords || []), ...(sw.th || [])])],\n\t['tr', new Set([...(natural.stopwords || []), ...(sw.tr || [])])],\n\t['uk', new Set([...(natural.stopwords || []), ...(sw.uk || [])])],\n\t['vi', new Set([...(natural.stopwords || []), ...(sw.vi || [])])],\n\t['zh', new Set([...(natural.stopwords || []), ...(sw.zh || [])])],\n]);\n\n// URL ending reserved characters\nconst URL_ENDING_RESERVED_CHARS = /[).,;!?]+$/;\n\n// Date pattern detection (DONE)\nconst DATE_PATTERNS = [\n\t/\\b(?:\\d{1,2}[/-]){2}\\d{2,4}\\b/g, // MM/DD/YYYY or DD/MM/YYYY\n\t/\\b\\d{4}(?:[/-]\\d{1,2}){2}\\b/g, // YYYY/MM/DD\n\t/\\b\\d{1,2}\\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\s+\\d{2,4}\\b/gi, // DD MMM YYYY\n\t/\\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\\s+\\d{1,2},?\\s+\\d{2,4}\\b/gi, // MMM DD, YYYY\n];\n\n// File path detection (DONE)\nconst FILE_PATH_PATTERNS = [\n\t/[a-z]:\\\\\\\\[^\\\\s<>:\"|?*]+/gi, // Windows paths\n\t/\\/[^\\\\s<>:\"|?*]+/g, // Unix paths\n\t/~\\/[^\\\\s<>:\"|?*]+/g, // Home directory paths\n];\n\n// Additional regex patterns\nconst CREDIT_CARD_PATTERN = creditCardRegex({exact: false});\nconst PHONE_PATTERN = phoneRegex({exact: false});\nconst EMAIL_PATTERN = emailRegexSafe({exact: false});\nconst IP_PATTERN = ipRegex({exact: false});\nconst URL_PATTERN = urlRegexSafe({exact: false});\nconst BITCOIN_PATTERN = bitcoinRegex({exact: false});\nconst MAC_PATTERN = macRegex({exact: false});\nconst HEX_COLOR_PATTERN = hexaColorRegex({exact: false});\nconst FLOATING_POINT_PATTERN = floatingPointRegex;\n\nclass SpamScanner {\n\tconstructor(options = {}) {\n\t\tthis.config = {\n\t\t\t// Enhanced configuration options\n\t\t\tenableMacroDetection: true,\n\t\t\tenablePerformanceMetrics: false,\n\t\t\ttimeout: 30_000,\n\t\t\tsupportedLanguages: ['en'],\n\t\t\tenableMixedLanguageDetection: false,\n\t\t\tenableAdvancedPatternRecognition: true,\n\n\t\t\t// Authentication options (mailauth)\n\t\t\tenableAuthentication: false,\n\t\t\tauthOptions: {\n\t\t\t\tip: null, // Remote IP address (required for auth)\n\t\t\t\thelo: null, // HELO/EHLO hostname\n\t\t\t\tmta: 'spamscanner', // MTA hostname\n\t\t\t\tsender: null, // Envelope sender (MAIL FROM)\n\t\t\t\ttimeout: 10_000, // DNS lookup timeout\n\t\t\t},\n\t\t\tauthScoreWeights: {\n\t\t\t\tdkimPass: -2,\n\t\t\t\tdkimFail: 3,\n\t\t\t\tspfPass: -1,\n\t\t\t\tspfFail: 2,\n\t\t\t\tspfSoftfail: 1,\n\t\t\t\tdmarcPass: -2,\n\t\t\t\tdmarcFail: 4,\n\t\t\t\tarcPass: -1,\n\t\t\t\tarcFail: 1,\n\t\t\t},\n\n\t\t\t// Reputation API options (Forward Email)\n\t\t\tenableReputation: false,\n\t\t\treputationOptions: {\n\t\t\t\tapiUrl: 'https://api.forwardemail.net/v1/reputation',\n\t\t\t\ttimeout: 10_000,\n\t\t\t\tonlyAligned: true,\n\t\t\t},\n\n\t\t\t// Arbitrary spam detection options\n\t\t\tenableArbitraryDetection: true,\n\t\t\tarbitraryThreshold: 5,\n\n\t\t\t// Existing options\n\t\t\tdebug: false,\n\t\t\tlogger: console,\n\t\t\tclamscan: {\n\t\t\t\tremoveInfected: false,\n\t\t\t\tquarantineInfected: false,\n\t\t\t\tscanLog: null,\n\t\t\t\tdebugMode: false,\n\t\t\t\tfileList: null,\n\t\t\t\tscanRecursively: true,\n\t\t\t\tclamscanPath: '/usr/bin/clamscan',\n\t\t\t\tclamdscanPath: '/usr/bin/clamdscan',\n\t\t\t\tpreference: 'clamdscan',\n\t\t\t},\n\t\t\tclassifier: null,\n\t\t\treplacements: null,\n\t\t\t...options,\n\t\t};\n\n\t\t// Async loading of replacements and classifier\n\t\tthis.classifier = null;\n\t\tthis.clamscan = null;\n\t\tthis.isInitialized = false;\n\n\t\t// Initialize replacements as empty Map\n\t\tthis.replacements = new Map();\n\n\t\t// Performance metrics\n\t\tthis.metrics = {\n\t\t\ttotalScans: 0,\n\t\t\taverageTime: 0,\n\t\t\tlastScanTime: 0,\n\t\t};\n\n\t\t// Bind methods\n\t\tautoBind(this);\n\t}\n\n\tasync initializeClassifier() {\n\t\tif (this.classifier) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tif (this.config.classifier) {\n\t\t\t\tthis.classifier = new NaiveBayes(this.config.classifier);\n\t\t\t} else {\n\t\t\t\tconst classifierData = await getClassifier();\n\t\t\t\tthis.classifier = new NaiveBayes(classifierData);\n\t\t\t}\n\n\t\t\t// Custom tokenizer - we handle tokenization ourselves\n\t\t\tthis.classifier.tokenizer = function (tokens) {\n\t\t\t\tif (typeof tokens === 'string') {\n\t\t\t\t\treturn tokens.split(/\\s+/);\n\t\t\t\t}\n\n\t\t\t\treturn Array.isArray(tokens) ? tokens : [];\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tdebug('Failed to initialize classifier:', error);\n\t\t\t// Create a fallback classifier\n\t\t\tthis.classifier = new NaiveBayes();\n\t\t}\n\t}\n\n\t// Initialize replacements\n\tasync initializeReplacements() {\n\t\tif (this.replacements && this.replacements.size > 0) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst replacements = this.config.replacements || await getReplacements();\n\n\t\t\t// Ensure replacements is a Map\n\t\t\tif (replacements instanceof Map) {\n\t\t\t\tthis.replacements = replacements;\n\t\t\t} else if (typeof replacements === 'object' && replacements !== null) {\n\t\t\t\tthis.replacements = new Map(Object.entries(replacements));\n\t\t\t} else {\n\t\t\t\tthrow new Error('Invalid replacements format');\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdebug('Failed to initialize replacements:', error);\n\t\t\t// Generate fallback replacements\n\t\t\tthis.replacements = new Map();\n\n\t\t\t// Add some basic replacements\n\t\t\tconst basicReplacements = {\n\t\t\t\tu: 'you',\n\t\t\t\tur: 'your',\n\t\t\t\tr: 'are',\n\t\t\t\tn: 'and',\n\t\t\t\t'w/': 'with',\n\t\t\t\tb4: 'before',\n\t\t\t\t2: 'to',\n\t\t\t\t4: 'for',\n\t\t\t};\n\n\t\t\tfor (const [word, replacement] of Object.entries(basicReplacements)) {\n\t\t\t\tthis.replacements.set(word, replacement);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Initialize regex helpers\n\tinitializeRegex() {\n\t\tthis.regexCache = new Map();\n\t\tthis.urlCache = new Map();\n\t}\n\n\t// Enhanced virus scanning with timeout protection\n\tasync getVirusResults(mail) {\n\t\tif (!this.clamscan) {\n\t\t\ttry {\n\t\t\t\tthis.clamscan = await new ClamScan().init(this.config.clamscan);\n\t\t\t} catch (error) {\n\t\t\t\tdebug('ClamScan initialization failed:', error);\n\t\t\t\treturn [];\n\t\t\t}\n\t\t}\n\n\t\tconst results = [];\n\t\tconst attachments = mail.attachments || [];\n\n\t\tfor (const attachment of attachments) {\n\t\t\ttry {\n\t\t\t\tif (attachment.content && isBuffer(attachment.content)) {\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst scanResult = await Promise.race([\n\t\t\t\t\t\tthis.clamscan.scanBuffer(attachment.content),\n\t\t\t\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\t\t\t\tsetTimeout(() => reject(new Error('Virus scan timeout')), this.config.timeout);\n\t\t\t\t\t\t}),\n\t\t\t\t\t]);\n\n\t\t\t\t\tif (scanResult.isInfected) {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\tfilename: attachment.filename || 'unknown',\n\t\t\t\t\t\t\tvirus: scanResult.viruses || ['Unknown virus'],\n\t\t\t\t\t\t\ttype: 'virus',\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tdebug('Virus scan error:', error);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Macro detection (DONE)\n\tasync getMacroResults(mail) {\n\t\tconst results = [];\n\t\tconst attachments = mail.attachments || [];\n\t\tconst textContent = mail.text || '';\n\t\tconst htmlContent = mail.html || '';\n\n\t\t// VBA Macro detection\n\t\tconst vbaPatterns = [\n\t\t\t/sub\\s+\\w+\\s*\\(/gi,\n\t\t\t/function\\s+\\w+\\s*\\(/gi,\n\t\t\t/dim\\s+\\w+\\s+as\\s+\\w+/gi,\n\t\t\t/application\\.run/gi,\n\t\t\t/shell\\s*\\(/gi,\n\t\t];\n\n\t\t// PowerShell detection\n\t\tconst powershellPatterns = [\n\t\t\t/powershell/gi,\n\t\t\t/invoke-expression/gi,\n\t\t\t/iex\\s*\\(/gi,\n\t\t\t/start-process/gi,\n\t\t\t/new-object\\s+system\\./gi,\n\t\t];\n\n\t\t// JavaScript macro detection\n\t\tconst jsPatterns = [\n\t\t\t/eval\\s*\\(/gi,\n\t\t\t/document\\.write/gi,\n\t\t\t/activexobject/gi,\n\t\t\t/wscript\\./gi,\n\t\t\t/new\\s+activexobject/gi,\n\t\t];\n\n\t\t// Batch file detection\n\t\tconst batchPatterns = [/@echo\\s+off/gi, /cmd\\s*\\/c/gi, /start\\s+\\/b/gi, /for\\s+\\/[lrf]/gi];\n\n\t\t// Get content from text, html, and header lines\n\t\tlet allContent = textContent + ' ' + htmlContent;\n\n\t\t// Also check header lines for content (like macro code in raw emails)\n\t\tif (mail.headerLines && Array.isArray(mail.headerLines)) {\n\t\t\tfor (const headerLine of mail.headerLines) {\n\t\t\t\tif (headerLine.line) {\n\t\t\t\t\tallContent += ' ' + headerLine.line;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for VBA macros\n\t\tfor (const pattern of vbaPatterns) {\n\t\t\tif (pattern.test(allContent)) {\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'macro',\n\t\t\t\t\tsubtype: 'vba',\n\t\t\t\t\tdescription: 'VBA macro detected',\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Check for PowerShell\n\t\tfor (const pattern of powershellPatterns) {\n\t\t\tif (pattern.test(allContent)) {\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'macro',\n\t\t\t\t\tsubtype: 'powershell',\n\t\t\t\t\tdescription: 'PowerShell script detected',\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Check for JavaScript macros\n\t\tfor (const pattern of jsPatterns) {\n\t\t\tif (pattern.test(allContent)) {\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'macro',\n\t\t\t\t\tsubtype: 'javascript',\n\t\t\t\t\tdescription: 'JavaScript macro detected',\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Check for batch files\n\t\tfor (const pattern of batchPatterns) {\n\t\t\tif (pattern.test(allContent)) {\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'macro',\n\t\t\t\t\tsubtype: 'batch',\n\t\t\t\t\tdescription: 'Batch script detected',\n\t\t\t\t});\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Check attachments for macro content\n\t\tfor (const attachment of attachments) {\n\t\t\tif (attachment.filename) {\n\t\t\t\tconst extension = fileExtension(attachment.filename).toLowerCase();\n\n\t\t\t\t// Standalone macro files\n\t\t\t\tconst macroExtensions = ['vbs', 'vba', 'ps1', 'bat', 'cmd', 'scr', 'pif'];\n\n\t\t\t\t// Office documents with macros (OOXML format)\n\t\t\t\tconst officeMacroExtensions = ['docm', 'xlsm', 'pptm', 'xlam', 'dotm', 'xltm', 'potm'];\n\n\t\t\t\t// Legacy Office formats (always have macro capability)\n\t\t\t\tconst legacyOfficeExtensions = ['doc', 'xls', 'ppt', 'dot', 'xlt', 'pot', 'xla', 'ppa'];\n\n\t\t\t\tif (macroExtensions.includes(extension)) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'macro',\n\t\t\t\t\t\tsubtype: 'script',\n\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\tdescription: `Macro script attachment detected: ${extension}`,\n\t\t\t\t\t});\n\t\t\t\t} else if (officeMacroExtensions.includes(extension)) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'macro',\n\t\t\t\t\t\tsubtype: 'office_document',\n\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\tdescription: `Office document with macro capability: ${extension}`,\n\t\t\t\t\t});\n\t\t\t\t} else if (legacyOfficeExtensions.includes(extension)) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'macro',\n\t\t\t\t\t\tsubtype: 'legacy_office',\n\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\tdescription: `Legacy Office document (macro-capable): ${extension}`,\n\t\t\t\t\t\trisk: 'high',\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Check PDF attachments for JavaScript\n\t\t\t\tif (extension === 'pdf' && attachment.content && isBuffer(attachment.content)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst pdfContent = attachment.content.toString('latin1');\n\t\t\t\t\t\t// Check for JavaScript in PDF\n\t\t\t\t\t\tif (pdfContent.includes('/JavaScript') || pdfContent.includes('/JS')) {\n\t\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t\ttype: 'macro',\n\t\t\t\t\t\t\t\tsubtype: 'pdf_javascript',\n\t\t\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\t\t\tdescription: 'PDF with embedded JavaScript detected',\n\t\t\t\t\t\t\t\trisk: 'medium',\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tdebug('PDF JavaScript detection error:', error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// File path detection (DONE)\n\tasync getFilePathResults(mail) {\n\t\tconst results = [];\n\t\tconst textContent = mail.text || '';\n\t\tconst htmlContent = mail.html || '';\n\t\tconst allContent = textContent + ' ' + htmlContent;\n\n\t\tfor (const pattern of FILE_PATH_PATTERNS) {\n\t\t\tconst matches = allContent.match(pattern);\n\t\t\tif (matches) {\n\t\t\t\tfor (const match of matches) {\n\t\t\t\t\t// Skip HTML tags and common false positives\n\t\t\t\t\tif (this.isValidFilePath(match)) {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\ttype: 'file_path',\n\t\t\t\t\t\t\tpath: match,\n\t\t\t\t\t\t\tdescription: 'Suspicious file path detected',\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Check if a path is a valid file path (not HTML tag or false positive)\n\tisValidFilePath(path) {\n\t\t// Skip HTML tags (common HTML elements)\n\t\tconst htmlTags = [\n\t\t\t'a',\n\t\t\t'abbr',\n\t\t\t'address',\n\t\t\t'area',\n\t\t\t'article',\n\t\t\t'aside',\n\t\t\t'audio',\n\t\t\t'b',\n\t\t\t'base',\n\t\t\t'bdi',\n\t\t\t'bdo',\n\t\t\t'blockquote',\n\t\t\t'body',\n\t\t\t'br',\n\t\t\t'button',\n\t\t\t'canvas',\n\t\t\t'caption',\n\t\t\t'cite',\n\t\t\t'code',\n\t\t\t'col',\n\t\t\t'colgroup',\n\t\t\t'data',\n\t\t\t'datalist',\n\t\t\t'dd',\n\t\t\t'del',\n\t\t\t'details',\n\t\t\t'dfn',\n\t\t\t'dialog',\n\t\t\t'div',\n\t\t\t'dl',\n\t\t\t'dt',\n\t\t\t'em',\n\t\t\t'embed',\n\t\t\t'fieldset',\n\t\t\t'figcaption',\n\t\t\t'figure',\n\t\t\t'footer',\n\t\t\t'form',\n\t\t\t'h1',\n\t\t\t'h2',\n\t\t\t'h3',\n\t\t\t'h4',\n\t\t\t'h5',\n\t\t\t'h6',\n\t\t\t'head',\n\t\t\t'header',\n\t\t\t'hr',\n\t\t\t'html',\n\t\t\t'i',\n\t\t\t'iframe',\n\t\t\t'img',\n\t\t\t'input',\n\t\t\t'ins',\n\t\t\t'kbd',\n\t\t\t'label',\n\t\t\t'legend',\n\t\t\t'li',\n\t\t\t'link',\n\t\t\t'main',\n\t\t\t'map',\n\t\t\t'mark',\n\t\t\t'meta',\n\t\t\t'meter',\n\t\t\t'nav',\n\t\t\t'noscript',\n\t\t\t'object',\n\t\t\t'ol',\n\t\t\t'optgroup',\n\t\t\t'option',\n\t\t\t'output',\n\t\t\t'p',\n\t\t\t'param',\n\t\t\t'picture',\n\t\t\t'pre',\n\t\t\t'progress',\n\t\t\t'q',\n\t\t\t'rp',\n\t\t\t'rt',\n\t\t\t'ruby',\n\t\t\t's',\n\t\t\t'samp',\n\t\t\t'script',\n\t\t\t'section',\n\t\t\t'select',\n\t\t\t'small',\n\t\t\t'source',\n\t\t\t'span',\n\t\t\t'strong',\n\t\t\t'style',\n\t\t\t'sub',\n\t\t\t'summary',\n\t\t\t'sup',\n\t\t\t'svg',\n\t\t\t'table',\n\t\t\t'tbody',\n\t\t\t'td',\n\t\t\t'template',\n\t\t\t'textarea',\n\t\t\t'tfoot',\n\t\t\t'th',\n\t\t\t'thead',\n\t\t\t'time',\n\t\t\t'title',\n\t\t\t'tr',\n\t\t\t'track',\n\t\t\t'u',\n\t\t\t'ul',\n\t\t\t'var',\n\t\t\t'video',\n\t\t\t'wbr',\n\t\t];\n\n\t\t// Check if it's an HTML tag\n\t\tconst tagMatch = path.match(/^\\/([a-z\\d]+)$/i);\n\t\tif (tagMatch && htmlTags.includes(tagMatch[1].toLowerCase())) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Skip very short paths that are likely false positives\n\t\tif (path.length < 4) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Skip paths that are just domain names\n\t\tif (/^\\/\\/[a-z\\d.-]+$/i.test(path)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Must have a file extension or be a directory with multiple segments\n\t\tif (!path.includes('.') && !path.includes('/')) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t// Optimize URL parsing with timeout protection (DONE)\n\tasync optimizeUrlParsing(url) {\n\t\ttry {\n\t\t\treturn await Promise.race([\n\t\t\t\tnormalizeUrl(url, {\n\t\t\t\t\tstripHash: true,\n\t\t\t\t\tstripWWW: false,\n\t\t\t\t\tremoveQueryParameters: false,\n\t\t\t\t}),\n\t\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\t\tsetTimeout(() => reject(new Error('URL parsing timeout')), 5000);\n\t\t\t\t}),\n\t\t\t]);\n\t\t} catch {\n\t\t\treturn url;\n\t\t}\n\t}\n\n\t// Enhanced Cloudflare blocked domain checking with timeout\n\tasync isCloudflareBlocked(hostname) {\n\t\ttry {\n\t\t\tconst response = await Promise.race([\n\t\t\t\tsuperagent\n\t\t\t\t\t.get(`https://1.1.1.3/dns-query?name=${hostname}&type=A`)\n\t\t\t\t\t.set('Accept', 'application/dns-json')\n\t\t\t\t\t.timeout(5000),\n\t\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\t\tsetTimeout(() => reject(new Error('DNS timeout')), 5000);\n\t\t\t\t}),\n\t\t\t]);\n\n\t\t\treturn response.body?.Status === 3; // NXDOMAIN indicates blocked\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Extract URLs from all possible sources\n\textractAllUrls(mail, originalSource) {\n\t\tlet allText = '';\n\n\t\t// Add mail text and html\n\t\tallText += (mail.text || '') + ' ' + (mail.html || '');\n\n\t\t// Add header lines content\n\t\tif (mail.headerLines && Array.isArray(mail.headerLines)) {\n\t\t\tfor (const headerLine of mail.headerLines) {\n\t\t\t\tif (headerLine.line) {\n\t\t\t\t\tallText += ' ' + headerLine.line;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Also check original source if it's a simple string\n\t\tif (typeof originalSource === 'string') {\n\t\t\tallText += ' ' + originalSource;\n\t\t}\n\n\t\treturn this.getUrls(allText);\n\t}\n\n\t// Enhanced URL extraction with improved parsing using tldts\n\tgetUrls(string_) {\n\t\tif (!isSANB(string_)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst urls = [];\n\t\tconst matches = string_.match(URL_PATTERN);\n\n\t\tif (matches) {\n\t\t\tfor (let url of matches) {\n\t\t\t\t// Clean up URL ending characters\n\t\t\t\turl = url.replace(URL_ENDING_RESERVED_CHARS, '');\n\n\t\t\t\t// Validate and normalize URL\n\t\t\t\ttry {\n\t\t\t\t\tconst normalizedUrl = normalizeUrl(url, {\n\t\t\t\t\t\tstripHash: false,\n\t\t\t\t\t\tstripWWW: false,\n\t\t\t\t\t});\n\t\t\t\t\turls.push(normalizedUrl);\n\t\t\t\t} catch {\n\t\t\t\t\t// If normalization fails, keep original\n\t\t\t\t\turls.push(url);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn [...new Set(urls)]; // Remove duplicates\n\t}\n\n\t// Parse URL using tldts for accurate domain extraction\n\tparseUrlWithTldts(url) {\n\t\ttry {\n\t\t\tconst parsed = parseTldts(url, {allowPrivateDomains: true});\n\t\t\treturn {\n\t\t\t\tdomain: parsed.domain,\n\t\t\t\tdomainWithoutSuffix: parsed.domainWithoutSuffix,\n\t\t\t\thostname: parsed.hostname,\n\t\t\t\tpublicSuffix: parsed.publicSuffix,\n\t\t\t\tsubdomain: parsed.subdomain,\n\t\t\t\tisIp: parsed.isIp,\n\t\t\t\tisIcann: parsed.isIcann,\n\t\t\t\tisPrivate: parsed.isPrivate,\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tdebug('tldts parsing error:', error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// Enhanced tokenization with language detection\n\tasync getTokens(string_, locale = 'en', isHtml = false) {\n\t\tif (!isSANB(string_)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tlet text = string_;\n\n\t\t// Strip HTML if needed\n\t\tif (isHtml) {\n\t\t\ttext = striptags(text);\n\t\t}\n\n\t\t// Detect language if not provided or if mixed language detection is enabled\n\t\tif (!locale || this.config.enableMixedLanguageDetection) {\n\t\t\ttry {\n\t\t\t\tconst detected = lande(text);\n\t\t\t\tif (detected && detected.length > 0) {\n\t\t\t\t\tlocale = detected[0][0];\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\tlocale ||= 'en';\n\t\t\t}\n\t\t}\n\n\t\t// Normalize locale\n\t\tlocale = this.parseLocale(locale);\n\n\t\t// Convert full-width to half-width characters\n\t\ttext = converter.toHalfWidth(text);\n\n\t\t// Expand contractions\n\t\ttry {\n\t\t\ttext = expandContractions(text);\n\t\t} catch {\n\t\t\t// If expansion fails, continue with original text\n\t\t}\n\n\t\t// Tokenize based on language\n\t\tlet tokens = [];\n\n\t\tif (locale === 'ja') {\n\t\t\t// Japanese tokenization\n\t\t\ttry {\n\t\t\t\ttokens = chineseTokenizer.tokenize(text);\n\t\t\t} catch {\n\t\t\t\ttokens = text.split(GENERIC_TOKENIZER);\n\t\t\t}\n\t\t} else if (locale === 'zh') {\n\t\t\t// Chinese tokenization\n\t\t\ttry {\n\t\t\t\ttokens = chineseTokenizer.tokenize(text);\n\t\t\t} catch {\n\t\t\t\ttokens = text.split(GENERIC_TOKENIZER);\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tokenization for other languages\n\t\t\ttokens = text.split(GENERIC_TOKENIZER);\n\t\t}\n\n\t\t// Process tokens\n\t\tlet processedTokens = tokens\n\t\t\t.map(token => token.toLowerCase().trim())\n\t\t\t.filter(token => token.length > 0 && token.length <= 50); // Reasonable length limit\n\n\t\t// Remove stopwords\n\t\tconst stopwordSet = stopwordsMap.get(locale) || stopwordsMap.get('en');\n\t\tif (stopwordSet) {\n\t\t\tprocessedTokens = processedTokens.filter(token => !stopwordSet.has(token));\n\t\t}\n\n\t\t// Stem words if available for the language\n\t\ttry {\n\t\t\tif (['en', 'es', 'fr', 'de', 'it', 'pt', 'ru'].includes(locale)) {\n\t\t\t\tprocessedTokens = processedTokens.map(token => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn snowball.stemword(token, locale);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn token;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t} catch {\n\t\t\t// If stemming fails, continue with original tokens\n\t\t}\n\n\t\t// Apply token hashing if enabled\n\t\tif (this.config.hashTokens) {\n\t\t\tprocessedTokens = processedTokens.map(token =>\n\t\t\t\tcreateHash('sha256')\n\t\t\t\t\t.update(token)\n\t\t\t\t\t.digest('hex')\n\t\t\t\t\t.slice(0, 16)); // Use first 16 characters for efficiency\n\t\t}\n\n\t\treturn processedTokens;\n\t}\n\n\t// Enhanced text preprocessing with pattern recognition\n\tasync preprocessText(string_) {\n\t\tif (!isSANB(string_)) {\n\t\t\treturn '';\n\t\t}\n\n\t\tlet text = string_;\n\n\t\t// Apply replacements if available\n\t\tif (this.replacements) {\n\t\t\tfor (const [original, replacement] of this.replacements) {\n\t\t\t\ttext = text.replaceAll(new RegExp(escapeStringRegexp(original), 'gi'), replacement);\n\t\t\t}\n\t\t}\n\n\t\t// Advanced pattern recognition (DONE)\n\t\tif (this.config.enableAdvancedPatternRecognition) {\n\t\t\t// Replace patterns with normalized tokens\n\t\t\ttext = text.replaceAll(DATE_PATTERNS[0], ' DATE_PATTERN ');\n\t\t\ttext = text.replace(CREDIT_CARD_PATTERN, ' CREDIT_CARD ');\n\t\t\ttext = text.replace(PHONE_PATTERN, ' PHONE_NUMBER ');\n\t\t\ttext = text.replace(EMAIL_PATTERN, ' EMAIL_ADDRESS ');\n\t\t\ttext = text.replace(IP_PATTERN, ' IP_ADDRESS ');\n\t\t\ttext = text.replace(URL_PATTERN, ' URL_LINK ');\n\t\t\ttext = text.replace(BITCOIN_PATTERN, ' BITCOIN_ADDRESS ');\n\t\t\ttext = text.replace(MAC_PATTERN, ' MAC_ADDRESS ');\n\t\t\ttext = text.replace(HEX_COLOR_PATTERN, ' HEX_COLOR ');\n\t\t\ttext = text.replace(FLOATING_POINT_PATTERN, ' FLOATING_POINT ');\n\t\t}\n\n\t\treturn text;\n\t}\n\n\t// Main scan method - enhanced with performance metrics, auth, and reputation\n\tasync scan(source, scanOptions = {}) {\n\t\tconst startTime = Date.now();\n\n\t\ttry {\n\t\t\t// Initialize components if needed\n\t\t\tawait this.initializeClassifier();\n\t\t\tawait this.initializeReplacements();\n\n\t\t\t// Get tokens and mail from source\n\t\t\tconst {tokens, mail} = await this.getTokensAndMailFromSource(source);\n\n\t\t\t// Merge scan options with config\n\t\t\tconst authOptions = {...this.config.authOptions, ...scanOptions.authOptions};\n\t\t\tconst reputationOptions = {...this.config.reputationOptions, ...scanOptions.reputationOptions};\n\n\t\t\t// Run all detection methods in parallel\n\t\t\tconst detectionPromises = [\n\t\t\t\tthis.getClassification(tokens),\n\t\t\t\tthis.getPhishingResults(mail),\n\t\t\t\tthis.getExecutableResults(mail),\n\t\t\t\tthis.config.enableMacroDetection ? this.getMacroResults(mail) : [],\n\t\t\t\tthis.config.enableArbitraryDetection ? this.getArbitraryResults(mail, {remoteAddress: authOptions.ip, resolvedClientHostname: authOptions.hostname}) : [],\n\t\t\t\tthis.getVirusResults(mail),\n\t\t\t\tthis.getPatternResults(mail),\n\t\t\t\tthis.getIDNHomographResults(mail),\n\t\t\t\tthis.getToxicityResults(mail),\n\t\t\t\tthis.getNSFWResults(mail),\n\t\t\t];\n\n\t\t\t// Add authentication check if enabled\n\t\t\tconst enableAuth = scanOptions.enableAuthentication ?? this.config.enableAuthentication;\n\t\t\tif (enableAuth && authOptions.ip) {\n\t\t\t\tdetectionPromises.push(this.getAuthenticationResults(source, mail, authOptions));\n\t\t\t} else {\n\t\t\t\tdetectionPromises.push(Promise.resolve(null));\n\t\t\t}\n\n\t\t\t// Add reputation check if enabled\n\t\t\tconst enableReputation = scanOptions.enableReputation ?? this.config.enableReputation;\n\t\t\tif (enableReputation) {\n\t\t\t\tdetectionPromises.push(this.getReputationResults(mail, authOptions, reputationOptions));\n\t\t\t} else {\n\t\t\t\tdetectionPromises.push(Promise.resolve(null));\n\t\t\t}\n\n\t\t\tconst [\n\t\t\t\tclassification,\n\t\t\t\tphishing,\n\t\t\t\texecutables,\n\t\t\t\tmacros,\n\t\t\t\tarbitrary,\n\t\t\t\tviruses,\n\t\t\t\tpatterns,\n\t\t\t\tidnHomographAttack,\n\t\t\t\ttoxicity,\n\t\t\t\tnsfw,\n\t\t\t\tauthResult,\n\t\t\t\treputationResult,\n\t\t\t] = await Promise.all(detectionPromises);\n\n\t\t\t// Determine if spam (considering reputation)\n\t\t\tlet isSpam\n\t\t\t\t= classification.category === 'spam'\n\t\t\t\t\t|| phishing.length > 0\n\t\t\t\t\t|| executables.length > 0\n\t\t\t\t\t|| macros.length > 0\n\t\t\t\t\t|| arbitrary.length > 0\n\t\t\t\t\t|| viruses.length > 0\n\t\t\t\t\t|| patterns.length > 0\n\t\t\t\t\t|| (idnHomographAttack && idnHomographAttack.detected)\n\t\t\t\t\t|| toxicity.length > 0\n\t\t\t\t\t|| nsfw.length > 0;\n\n\t\t\t// Override spam status based on reputation\n\t\t\t// Only denylist should override - truth source and allowlist are informational only\n\t\t\tif (reputationResult && reputationResult.isDenylisted) {\n\t\t\t\tisSpam = true;\n\t\t\t}\n\n\t\t\t// Generate message\n\t\t\tlet message = 'Ham';\n\t\t\tif (isSpam) {\n\t\t\t\tconst reasons = [];\n\t\t\t\tif (classification.category === 'spam') {\n\t\t\t\t\treasons.push('spam classification');\n\t\t\t\t}\n\n\t\t\t\tif (phishing.length > 0) {\n\t\t\t\t\treasons.push('phishing detected');\n\t\t\t\t}\n\n\t\t\t\tif (executables.length > 0) {\n\t\t\t\t\treasons.push('executable content');\n\t\t\t\t}\n\n\t\t\t\tif (macros.length > 0) {\n\t\t\t\t\treasons.push('macro detected');\n\t\t\t\t}\n\n\t\t\t\tif (arbitrary.length > 0) {\n\t\t\t\t\treasons.push('arbitrary patterns');\n\t\t\t\t}\n\n\t\t\t\tif (viruses.length > 0) {\n\t\t\t\t\treasons.push('virus detected');\n\t\t\t\t}\n\n\t\t\t\tif (patterns.length > 0) {\n\t\t\t\t\treasons.push('suspicious patterns');\n\t\t\t\t}\n\n\t\t\t\tif (idnHomographAttack && idnHomographAttack.detected) {\n\t\t\t\t\treasons.push('IDN homograph attack');\n\t\t\t\t}\n\n\t\t\t\tif (toxicity.length > 0) {\n\t\t\t\t\treasons.push('toxic content');\n\t\t\t\t}\n\n\t\t\t\tif (nsfw.length > 0) {\n\t\t\t\t\treasons.push('NSFW content');\n\t\t\t\t}\n\n\t\t\t\tif (reputationResult?.isDenylisted) {\n\t\t\t\t\treasons.push('denylisted sender');\n\t\t\t\t}\n\n\t\t\t\tmessage = `Spam (${arrayJoinConjunction(reasons)})`;\n\t\t\t} else if (reputationResult?.isTruthSource) {\n\t\t\t\tmessage = 'Ham (truth source)';\n\t\t\t} else if (reputationResult?.isAllowlisted) {\n\t\t\t\tmessage = 'Ham (allowlisted)';\n\t\t\t}\n\n\t\t\tconst endTime = Date.now();\n\t\t\tconst processingTime = endTime - startTime;\n\n\t\t\t// Update metrics\n\t\t\tthis.metrics.totalScans++;\n\t\t\tthis.metrics.lastScanTime = processingTime;\n\t\t\tthis.metrics.averageTime\n\t\t\t\t= ((this.metrics.averageTime * (this.metrics.totalScans - 1)) + processingTime)\n\t\t\t\t\t/ this.metrics.totalScans;\n\n\t\t\tconst result = {\n\t\t\t\tisSpam,\n\t\t\t\tmessage,\n\t\t\t\tresults: {\n\t\t\t\t\tclassification,\n\t\t\t\t\tphishing,\n\t\t\t\t\texecutables,\n\t\t\t\t\tmacros,\n\t\t\t\t\tarbitrary,\n\t\t\t\t\tviruses,\n\t\t\t\t\tpatterns,\n\t\t\t\t\tidnHomographAttack,\n\t\t\t\t\ttoxicity,\n\t\t\t\t\tnsfw,\n\t\t\t\t\tauthentication: authResult,\n\t\t\t\t\treputation: reputationResult,\n\t\t\t\t},\n\t\t\t\tlinks: this.extractAllUrls(mail, source),\n\t\t\t\ttokens,\n\t\t\t\tmail,\n\t\t\t};\n\n\t\t\t// Add performance metrics if enabled\n\t\t\tif (this.config.enablePerformanceMetrics) {\n\t\t\t\tresult.metrics = {\n\t\t\t\t\ttotalTime: processingTime,\n\t\t\t\t\tclassificationTime: 0, // Would need to measure individually\n\t\t\t\t\tphishingTime: 0,\n\t\t\t\t\texecutableTime: 0,\n\t\t\t\t\tmacroTime: 0,\n\t\t\t\t\tvirusTime: 0,\n\t\t\t\t\tpatternTime: 0,\n\t\t\t\t\tidnTime: 0,\n\t\t\t\t\tmemoryUsage: process.memoryUsage(),\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tdebug('Scan error:', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// Get authentication results using mailauth\n\tasync getAuthenticationResults(source, mail, options = {}) {\n\t\ttry {\n\t\t\t// Get raw message buffer\n\t\t\tconst messageBuffer = typeof source === 'string'\n\t\t\t\t? Buffer.from(source)\n\t\t\t\t: source;\n\n\t\t\t// Extract sender from mail if not provided\n\t\t\tconst sender = options.sender\n\t\t\t\t|| mail.from?.value?.[0]?.address\n\t\t\t\t|| mail.from?.text;\n\n\t\t\t// Authenticate the message\n\t\t\tconst authResult = await authenticate(messageBuffer, {\n\t\t\t\tip: options.ip,\n\t\t\t\thelo: options.helo,\n\t\t\t\tmta: options.mta || 'spamscanner',\n\t\t\t\tsender,\n\t\t\t\ttimeout: options.timeout || 10_000,\n\t\t\t});\n\n\t\t\t// Calculate auth score\n\t\t\tconst scoreResult = calculateAuthScore(authResult, this.config.authScoreWeights);\n\n\t\t\treturn {\n\t\t\t\t...authResult,\n\t\t\t\tscore: scoreResult,\n\t\t\t\tauthResultsHeader: formatAuthResultsHeader(authResult, options.mta || 'spamscanner'),\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tdebug('Authentication error:', error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// Get reputation results from Forward Email API\n\t// Uses get-attributes module to extract comprehensive attributes for checking\n\tasync getReputationResults(mail, authOptions = {}, reputationOptions = {}) {\n\t\ttry {\n\t\t\t// Use extractAttributes for comprehensive attribute extraction\n\t\t\t// This follows Forward Email's get-attributes.js pattern\n\t\t\tconst {attributes, session} = await extractAttributes(mail, {\n\t\t\t\tisAligned: reputationOptions.onlyAligned ?? true,\n\t\t\t\tsenderIp: authOptions.ip,\n\t\t\t\tsenderHostname: authOptions.hostname,\n\t\t\t\tauthResults: authOptions.authResults,\n\t\t\t});\n\n\t\t\t// Add any additional values from authOptions that weren't extracted\n\t\t\tconst valuesToCheck = [...attributes];\n\n\t\t\t// Add envelope sender if provided and not already included\n\t\t\tif (authOptions.sender) {\n\t\t\t\tconst senderLower = authOptions.sender.toLowerCase();\n\t\t\t\tif (!valuesToCheck.includes(senderLower)) {\n\t\t\t\t\tvaluesToCheck.push(senderLower);\n\t\t\t\t}\n\n\t\t\t\t// Add envelope sender domain\n\t\t\t\tconst envelopeDomain = senderLower.split('@')[1];\n\t\t\t\tif (envelopeDomain && !valuesToCheck.includes(envelopeDomain)) {\n\t\t\t\t\tvaluesToCheck.push(envelopeDomain);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add Reply-To addresses if not already included\n\t\t\tconst replyTo = mail.replyTo?.value || [];\n\t\t\tfor (const addr of replyTo) {\n\t\t\t\tif (addr.address) {\n\t\t\t\t\tconst addrLower = addr.address.toLowerCase();\n\t\t\t\t\tif (!valuesToCheck.includes(addrLower)) {\n\t\t\t\t\t\tvaluesToCheck.push(addrLower);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst domain = addrLower.split('@')[1];\n\t\t\t\t\tif (domain && !valuesToCheck.includes(domain)) {\n\t\t\t\t\t\tvaluesToCheck.push(domain);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (valuesToCheck.length === 0) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\tdebug('Checking reputation for %d attributes: %o', valuesToCheck.length, valuesToCheck);\n\n\t\t\t// Check reputation for all values in parallel\n\t\t\tconst resultsMap = await checkReputationBatch(valuesToCheck, reputationOptions);\n\n\t\t\t// Aggregate results\n\t\t\tconst aggregated = aggregateReputationResults([...resultsMap.values()]);\n\n\t\t\treturn {\n\t\t\t\t...aggregated,\n\t\t\t\tcheckedValues: valuesToCheck,\n\t\t\t\tdetails: Object.fromEntries(resultsMap),\n\t\t\t\tsession, // Include session info for debugging\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tdebug('Reputation check error:', error);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\t// Get pattern recognition results\n\tasync getPatternResults(mail) {\n\t\tconst results = [];\n\t\tconst textContent = mail.text || '';\n\t\tconst htmlContent = mail.html || '';\n\t\tconst allContent = textContent + ' ' + htmlContent;\n\n\t\t// Date pattern detection\n\t\tfor (const pattern of DATE_PATTERNS) {\n\t\t\tconst matches = allContent.match(pattern);\n\t\t\tif (matches && matches.length > 5) {\n\t\t\t\t// Suspicious if many dates\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'pattern',\n\t\t\t\t\tsubtype: 'date_spam',\n\t\t\t\t\tcount: matches.length,\n\t\t\t\t\tdescription: 'Excessive date patterns detected',\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// File path detection\n\t\tconst filePathResults = await this.getFilePathResults(mail);\n\t\tresults.push(...filePathResults);\n\n\t\treturn results;\n\t}\n\n\t// Enhanced mail parsing with better error handling\n\tasync getTokensAndMailFromSource(source) {\n\t\tlet mail;\n\n\t\tif (typeof source === 'string' && fs.existsSync(source)) {\n\t\t\t// File path\n\t\t\tsource = fs.readFileSync(source);\n\t\t}\n\n\t\tif (isBuffer(source)) {\n\t\t\tsource = source.toString();\n\t\t}\n\n\t\tif (!source || typeof source !== 'string') {\n\t\t\tsource = '';\n\t\t}\n\n\t\ttry {\n\t\t\tmail = await simpleParser(source);\n\t\t} catch (error) {\n\t\t\tdebug('Mail parsing error:', error);\n\t\t\t// Create minimal mail object\n\t\t\tmail = {\n\t\t\t\ttext: source,\n\t\t\t\thtml: '',\n\t\t\t\tsubject: '',\n\t\t\t\tfrom: {},\n\t\t\t\tto: [],\n\t\t\t\tattachments: [],\n\t\t\t};\n\t\t}\n\n\t\t// Preprocess text content\n\t\tconst textContent = await this.preprocessText(mail.text || '');\n\t\tconst htmlContent = await this.preprocessText(striptags(mail.html || ''));\n\t\tconst subjectContent = await this.preprocessText(mail.subject || '');\n\n\t\t// Get tokens from all content\n\t\tconst allContent = [textContent, htmlContent, subjectContent].join(' ');\n\t\tconst tokens = await this.getTokens(allContent, 'en');\n\n\t\treturn {tokens, mail};\n\t}\n\n\t// Enhanced classification with better error handling\n\tasync getClassification(tokens) {\n\t\tif (!this.classifier) {\n\t\t\tawait this.initializeClassifier();\n\t\t}\n\n\t\ttry {\n\t\t\t// Join tokens into a string for the classifier\n\t\t\tconst text = Array.isArray(tokens) ? tokens.join(' ') : String(tokens);\n\t\t\tconst result = this.classifier.categorize(text);\n\n\t\t\treturn {\n\t\t\t\tcategory: result,\n\t\t\t\tprobability: 0.5, // Default probability\n\t\t\t};\n\t\t} catch (error) {\n\t\t\tdebug('Classification error:', error);\n\t\t\treturn {\n\t\t\t\tcategory: 'ham',\n\t\t\t\tprobability: 0.5,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Enhanced phishing detection\n\tasync getPhishingResults(mail) {\n\t\tconst results = [];\n\t\tconst links = this.getUrls(mail.text || '');\n\n\t\tfor (const url of links) {\n\t\t\ttry {\n\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\tconst normalizedUrl = await this.optimizeUrlParsing(url);\n\t\t\t\tconst parsed = new URL(normalizedUrl);\n\n\t\t\t\t// Check for suspicious domains\n\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\tconst isBlocked = await this.isCloudflareBlocked(parsed.hostname);\n\t\t\t\tif (isBlocked) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'phishing',\n\t\t\t\t\t\turl: normalizedUrl,\n\n\t\t\t\t\t\tdescription: 'Blocked by security filters',\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Enhanced IDN homograph attack detection\n\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\tconst idnDetector = await this.getIDNDetector();\n\t\t\t\tif (idnDetector && parsed.hostname) {\n\t\t\t\t\tconst context = {\n\t\t\t\t\t\temailContent: mail.text || mail.html || '',\n\t\t\t\t\t\tdisplayText: url === normalizedUrl ? null : url,\n\t\t\t\t\t\tsenderReputation: 0.5, // Default neutral reputation\n\t\t\t\t\t};\n\n\t\t\t\t\tconst idnAnalysis = idnDetector.detectHomographAttack(parsed.hostname, context);\n\n\t\t\t\t\tif (idnAnalysis.riskScore > 0.6) {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\ttype: 'phishing',\n\t\t\t\t\t\t\turl: normalizedUrl,\n\t\t\t\t\t\t\tdescription: `IDN homograph attack detected (risk: ${(idnAnalysis.riskScore * 100).toFixed(1)}%)`,\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\triskFactors: idnAnalysis.riskFactors,\n\t\t\t\t\t\t\t\trecommendations: idnAnalysis.recommendations,\n\t\t\t\t\t\t\t\tconfidence: idnAnalysis.confidence,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t} else if (idnAnalysis.riskScore > 0.3) {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\ttype: 'suspicious',\n\t\t\t\t\t\t\turl: normalizedUrl,\n\t\t\t\t\t\t\tdescription: `Suspicious IDN domain (risk: ${(idnAnalysis.riskScore * 100).toFixed(1)}%)`,\n\t\t\t\t\t\t\tdetails: {\n\t\t\t\t\t\t\t\triskFactors: idnAnalysis.riskFactors,\n\t\t\t\t\t\t\t\trecommendations: idnAnalysis.recommendations,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tdebug('Phishing check error:', error);\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Enhanced executable detection\n\tasync getExecutableResults(mail) {\n\t\tconst results = [];\n\t\tconst attachments = mail.attachments || [];\n\n\t\tfor (const attachment of attachments) {\n\t\t\tif (attachment.filename) {\n\t\t\t\tconst extension = fileExtension(attachment.filename).toLowerCase();\n\n\t\t\t\tif (EXECUTABLES.has(extension)) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'executable',\n\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\textension,\n\t\t\t\t\t\tdescription: 'Executable file attachment',\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Check for archive files (potential evasion technique)\n\t\t\t\tconst archiveExtensions = ['zip', 'rar', '7z', 'tar', 'gz', 'bz2', 'xz', 'arj', 'cab', 'lzh', 'ace', 'iso'];\n\t\t\t\tif (archiveExtensions.includes(extension)) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'archive',\n\t\t\t\t\t\tfilename: attachment.filename,\n\t\t\t\t\t\textension,\n\t\t\t\t\t\tdescription: 'Archive attachment (contents not scanned)',\n\t\t\t\t\t\trisk: 'medium',\n\t\t\t\t\t\twarning: 'Archive contents should be extracted and scanned separately',\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Check file content for executable signatures\n\t\t\tif (attachment.content && isBuffer(attachment.content)) {\n\t\t\t\ttry {\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst fileType = await fileTypeFromBuffer(attachment.content);\n\t\t\t\t\tif (fileType && EXECUTABLES.has(fileType.ext)) {\n\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\ttype: 'executable',\n\t\t\t\t\t\t\tfilename: attachment.filename || 'unknown',\n\t\t\t\t\t\t\tdetectedType: fileType.ext,\n\t\t\t\t\t\t\tdescription: 'Executable content detected',\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdebug('File type detection error:', error);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Arbitrary results (GTUBE, spam patterns, etc.)\n\t// Updated to use session info for Microsoft Exchange spam detection\n\tasync getArbitraryResults(mail, sessionInfo = {}) {\n\t\tconst results = [];\n\n\t\t// Get content from text, html, and header lines\n\t\tlet content = (mail.text || '') + (mail.html || '');\n\n\t\t// Also check header lines for content (like GTUBE in raw emails)\n\t\tif (mail.headerLines && Array.isArray(mail.headerLines)) {\n\t\t\tfor (const headerLine of mail.headerLines) {\n\t\t\t\tif (headerLine.line) {\n\t\t\t\t\tcontent += ' ' + headerLine.line;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// GTUBE test\n\t\tif (content.includes('XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X')) {\n\t\t\tresults.push({\n\t\t\t\ttype: 'arbitrary',\n\t\t\t\tsubtype: 'gtube',\n\t\t\t\tdescription: 'GTUBE spam test pattern detected',\n\t\t\t\tscore: 100,\n\t\t\t});\n\t\t}\n\n\t\t// Use is-arbitrary module for advanced spam pattern detection\n\t\t// Now includes Microsoft Exchange spam detection and vendor-specific checks\n\t\ttry {\n\t\t\t// Build session info from parsed email if not provided\n\t\t\tconst session = buildSessionInfo(mail, sessionInfo);\n\n\t\t\tconst arbitraryResult = isArbitrary(mail, {\n\t\t\t\tthreshold: this.config.arbitraryThreshold || 5,\n\t\t\t\tcheckSubject: true,\n\t\t\t\tcheckBody: true,\n\t\t\t\tcheckSender: true,\n\t\t\t\tcheckHeaders: true,\n\t\t\t\tcheckLinks: true,\n\t\t\t\tcheckMicrosoftHeaders: true, // Enable Microsoft Exchange spam detection\n\t\t\t\tcheckVendorSpam: true, // Enable vendor-specific spam detection\n\t\t\t\tcheckSpoofing: true, // Enable spoofing attack detection\n\t\t\t\tsession, // Pass session info for advanced checks\n\t\t\t});\n\n\t\t\tif (arbitraryResult.isArbitrary) {\n\t\t\t\tresults.push({\n\t\t\t\t\ttype: 'arbitrary',\n\t\t\t\t\tsubtype: arbitraryResult.category ? arbitraryResult.category.toLowerCase() : 'pattern',\n\t\t\t\t\tdescription: `Arbitrary spam patterns detected (score: ${arbitraryResult.score})`,\n\t\t\t\t\tscore: arbitraryResult.score,\n\t\t\t\t\treasons: arbitraryResult.reasons,\n\t\t\t\t\tcategory: arbitraryResult.category,\n\t\t\t\t});\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdebug('Arbitrary detection error:', error);\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Parse and normalize locale\n\tparseLocale(locale) {\n\t\tif (!locale || typeof locale !== 'string') {\n\t\t\treturn 'en';\n\t\t}\n\n\t\t// Handle locale codes like 'en-US' -> 'en'\n\t\tconst normalized = locale.toLowerCase().split('-')[0];\n\t\t// Map some common variations\n\t\tconst localeMap = {\n\t\t\tnb: 'no', // Norwegian Bokm\u00E5l\n\t\t\tnn: 'no', // Norwegian Nynorsk\n\t\t\t'zh-cn': 'zh',\n\t\t\t'zh-tw': 'zh',\n\t\t};\n\t\treturn localeMap[normalized] || normalized;\n\t}\n\n\t// Get IDN homograph attack results\n\tasync getIDNHomographResults(mail) {\n\t\tconst result = {\n\t\t\tdetected: false,\n\t\t\tdomains: [],\n\t\t\triskScore: 0,\n\t\t\tdetails: [],\n\t\t};\n\n\t\ttry {\n\t\t\tconst idnDetector = await this.getIDNDetector();\n\t\t\tif (!idnDetector) {\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\t// Extract URLs from email content\n\t\t\tconst textContent = mail.text || '';\n\t\t\tconst htmlContent = mail.html || '';\n\t\t\tconst allContent = textContent + ' ' + htmlContent;\n\t\t\tconst urls = this.getUrls(allContent);\n\n\t\t\t// Analyze each domain\n\t\t\tfor (const url of urls) {\n\t\t\t\ttry {\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst normalizedUrl = await this.optimizeUrlParsing(url);\n\t\t\t\t\tconst parsed = new URL(normalizedUrl);\n\t\t\t\t\tconst domain = parsed.hostname;\n\n\t\t\t\t\tif (!domain) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prepare context for analysis\n\t\t\t\t\tconst context = {\n\t\t\t\t\t\temailContent: allContent,\n\t\t\t\t\t\tdisplayText: url === normalizedUrl ? null : url,\n\t\t\t\t\t\tsenderReputation: 0.5, // Default neutral reputation\n\t\t\t\t\t\temailHeaders: mail.headers || {},\n\t\t\t\t\t};\n\n\t\t\t\t\t// Perform IDN analysis\n\t\t\t\t\tconst analysis = idnDetector.detectHomographAttack(domain, context);\n\n\t\t\t\t\tif (analysis.riskScore > 0.3) {\n\t\t\t\t\t\tresult.detected = true;\n\t\t\t\t\t\tresult.domains.push({\n\t\t\t\t\t\t\tdomain,\n\t\t\t\t\t\t\toriginalUrl: url,\n\t\t\t\t\t\t\tnormalizedUrl,\n\t\t\t\t\t\t\triskScore: analysis.riskScore,\n\t\t\t\t\t\t\triskFactors: analysis.riskFactors,\n\t\t\t\t\t\t\trecommendations: analysis.recommendations,\n\t\t\t\t\t\t\tconfidence: analysis.confidence,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Update overall risk score to highest found\n\t\t\t\t\t\tresult.riskScore = Math.max(result.riskScore, analysis.riskScore);\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdebug('IDN analysis error for URL:', url, error);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add summary details\n\t\t\tif (result.detected) {\n\t\t\t\tresult.details.push(\n\t\t\t\t\t`Found ${result.domains.length} suspicious domain(s)`,\n\t\t\t\t\t`Highest risk score: ${(result.riskScore * 100).toFixed(1)}%`,\n\t\t\t\t);\n\n\t\t\t\t// Add specific risk factors\n\t\t\t\tconst allRiskFactors = new Set();\n\t\t\t\tfor (const domain of result.domains) {\n\t\t\t\t\tfor (const factor of domain.riskFactors) {\n\t\t\t\t\t\tallRiskFactors.add(factor);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.details.push(...allRiskFactors);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdebug('IDN homograph detection error:', error);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t// Get IDN detector instance\n\tasync getIDNDetector() {\n\t\tif (!this.idnDetector) {\n\t\t\ttry {\n\t\t\t\tconst {default: EnhancedIDNDetector} = await import('./enhanced-idn-detector.js');\n\t\t\t\tthis.idnDetector = new EnhancedIDNDetector({\n\t\t\t\t\tstrictMode: this.config.strictIDNDetection || false,\n\t\t\t\t\tenableWhitelist: true,\n\t\t\t\t\tenableBrandProtection: true,\n\t\t\t\t\tenableContextAnalysis: true,\n\t\t\t\t});\n\t\t\t} catch (error) {\n\t\t\t\tdebug('Failed to load IDN detector:', error);\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\n\t\treturn this.idnDetector;\n\t}\n\n\t// Hybrid language detection using both lande and franc\n\tasync detectLanguageHybrid(text) {\n\t\tif (!text || typeof text !== 'string' || text.length < 3) {\n\t\t\treturn 'en';\n\t\t}\n\n\t\t// Handle edge cases for non-linguistic content\n\t\tconst cleanText = text.trim();\n\t\tif (!cleanText || /^[\\d\\s\\W]+$/.test(cleanText)) {\n\t\t\t// Only numbers, spaces, and special characters\n\t\t\treturn 'en';\n\t\t}\n\n\t\ttry {\n\t\t\t// Use lande for short text (< 50 chars), franc for longer text\n\t\t\tif (text.length < 50) {\n\t\t\t\tconst landeResult = lande(text);\n\t\t\t\tif (landeResult && landeResult.length > 0) {\n\t\t\t\t\t// Convert lande's 3-letter codes to 2-letter codes\n\t\t\t\t\tconst detected = landeResult[0][0];\n\t\t\t\t\tconst normalized = this.normalizeLanguageCode(detected);\n\n\t\t\t\t\t// Additional validation for short text detection\n\t\t\t\t\tif (this.isValidShortTextDetection(text, normalized)) {\n\t\t\t\t\t\t// Filter by supportedLanguages if configured\n\t\t\t\t\t\tif (this.config.supportedLanguages && this.config.supportedLanguages.length > 0) {\n\t\t\t\t\t\t\tif (this.config.supportedLanguages.includes(normalized)) {\n\t\t\t\t\t\t\t\treturn normalized;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\treturn this.config.supportedLanguages[0];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn normalized;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Fallback to English for ambiguous short text\n\t\t\t\t\treturn 'en';\n\t\t\t\t}\n\n\t\t\t\treturn 'en';\n\t\t\t}\n\n\t\t\t// Import franc dynamically\n\t\t\tconst {franc} = await import('franc');\n\t\t\tconst francResult = franc(text);\n\t\t\tif (francResult === 'und') {\n\t\t\t\t// Fallback to lande if franc can't detect\n\t\t\t\tconst landeResult = lande(text);\n\t\t\t\tif (landeResult && landeResult.length > 0) {\n\t\t\t\t\treturn this.normalizeLanguageCode(landeResult[0][0]);\n\t\t\t\t}\n\n\t\t\t\treturn 'en';\n\t\t\t}\n\n\t\t\tconst detected = this.normalizeLanguageCode(francResult);\n\n\t\t\t// Filter by supportedLanguages if configured\n\t\t\tif (this.config.supportedLanguages && this.config.supportedLanguages.length > 0) {\n\t\t\t\tif (this.config.supportedLanguages.includes(detected)) {\n\t\t\t\t\treturn detected;\n\t\t\t\t}\n\n\t\t\t\t// If detected language not supported, return first supported language\n\t\t\t\treturn this.config.supportedLanguages[0];\n\t\t\t}\n\n\t\t\treturn detected;\n\t\t} catch (error) {\n\t\t\tdebug('Language detection error:', error);\n\t\t\t// Fallback to lande\n\t\t\ttry {\n\t\t\t\tconst landeResult = lande(text);\n\t\t\t\tif (landeResult && landeResult.length > 0) {\n\t\t\t\t\treturn this.normalizeLanguageCode(landeResult[0][0]);\n\t\t\t\t}\n\n\t\t\t\treturn 'en';\n\t\t\t} catch {\n\t\t\t\treturn 'en';\n\t\t\t}\n\t\t}\n\t}\n\n\t// Validate short text language detection\n\tisValidShortTextDetection(text, detectedLang) {\n\t\t// For non-Latin scripts, always trust the detection\n\t\t// eslint-disable-next-line no-control-regex\n\t\tconst hasNonLatin = /[^\\u0000-\\u024F\\u1E00-\\u1EFF]/.test(text);\n\t\tif (hasNonLatin) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// For very short Latin text (< 7 chars), be conservative\n\t\tif (text.length < 7 && detectedLang !== 'en') {\n\t\t\treturn false;\n\t\t}\n\n\t\t// For longer Latin text, trust the detection\n\t\treturn true;\n\t}\n\n\t// Get toxicity detection results\n\tasync getToxicityResults(mail) {\n\t\tconst results = [];\n\n\t\ttry {\n\t\t\t// Lazy load TensorFlow and toxicity model\n\t\t\tif (!this.toxicityModel) {\n\t\t\t\tconst _tf = await import('@tensorflow/tfjs-node');\n\t\t\t\tconst toxicity = await import('@tensorflow-models/toxicity');\n\n\t\t\t\t// Load model with threshold of 0.7 (higher = more strict)\n\t\t\t\tconst threshold = this.config.toxicityThreshold || 0.7;\n\t\t\t\tthis.toxicityModel = await toxicity.load(threshold);\n\t\t\t}\n\n\t\t\t// Get text content from email\n\t\t\tconst textContent = mail.text || '';\n\t\t\tconst htmlContent = striptags(mail.html || '');\n\t\t\tconst subjectContent = mail.subject || '';\n\n\t\t\t// Combine all content\n\t\t\tconst allContent = [subjectContent, textContent, htmlContent]\n\t\t\t\t.filter(text => text && text.trim().length > 0)\n\t\t\t\t.join(' ')\n\t\t\t\t.slice(0, 5000); // Limit to 5000 chars for performance\n\n\t\t\tif (!allContent || allContent.trim().length < 10) {\n\t\t\t\treturn results;\n\t\t\t}\n\n\t\t\t// Classify text for toxicity\n\t\t\tconst predictions = await Promise.race([\n\t\t\t\tthis.toxicityModel.classify([allContent]),\n\t\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\t\tsetTimeout(() => reject(new Error('Toxicity detection timeout')), this.config.timeout);\n\t\t\t\t}),\n\t\t\t]);\n\n\t\t\t// Process predictions\n\t\t\tfor (const prediction of predictions) {\n\t\t\t\tconst {label} = prediction;\n\t\t\t\tconst matches = prediction.results[0].match;\n\n\t\t\t\tif (matches) {\n\t\t\t\t\tconst {probabilities} = prediction.results[0];\n\t\t\t\t\tconst toxicProbability = probabilities[1]; // Index 1 is toxic probability\n\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\ttype: 'toxicity',\n\t\t\t\t\t\tcategory: label,\n\t\t\t\t\t\tprobability: toxicProbability,\n\t\t\t\t\t\tdescription: `Toxic content detected: ${label} (${(toxicProbability * 100).toFixed(1)}%)`,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdebug('Toxicity detection error:', error);\n\t\t\t// Don't fail the whole scan if toxicity detection fails\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Get NSFW image detection results\n\tasync getNSFWResults(mail) {\n\t\tconst results = [];\n\n\t\ttry {\n\t\t\t// Lazy load TensorFlow and NSFW model\n\t\t\tif (!this.nsfwModel) {\n\t\t\t\tconst _tf = await import('@tensorflow/tfjs-node');\n\t\t\t\tconst nsfw = await import('nsfwjs');\n\n\t\t\t\t// Load model\n\t\t\t\tthis.nsfwModel = await nsfw.load();\n\t\t\t}\n\n\t\t\tconst attachments = mail.attachments || [];\n\n\t\t\t// Process image attachments\n\t\t\tfor (const attachment of attachments) {\n\t\t\t\ttry {\n\t\t\t\t\tif (!attachment.content || !isBuffer(attachment.content)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if it's an image\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst fileType = await fileTypeFromBuffer(attachment.content);\n\n\t\t\t\t\tif (!fileType || !fileType.mime.startsWith('image/')) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Use sharp to process image for NSFW detection\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop, unicorn/no-await-expression-member\n\t\t\t\t\tconst sharp = (await import('sharp')).default;\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst processedImage = await sharp(attachment.content)\n\t\t\t\t\t\t.resize(224, 224) // NSFW model expects 224x224\n\t\t\t\t\t\t.raw()\n\t\t\t\t\t\t.toBuffer();\n\n\t\t\t\t\t// Convert to tensor and classify\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst _tf = await import('@tensorflow/tfjs-node');\n\t\t\t\t\tconst imageTensor = _tf.tensor3d(\n\t\t\t\t\t\tnew Uint8Array(processedImage),\n\t\t\t\t\t\t[224, 224, 3],\n\t\t\t\t\t);\n\n\t\t\t\t\t// eslint-disable-next-line no-await-in-loop\n\t\t\t\t\tconst predictions = await Promise.race([\n\t\t\t\t\t\tthis.nsfwModel.classify(imageTensor),\n\t\t\t\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\t\t\t\tsetTimeout(() => reject(new Error('NSFW detection timeout')), this.config.timeout);\n\t\t\t\t\t\t}),\n\t\t\t\t\t]);\n\n\t\t\t\t\t// Clean up tensor\n\t\t\t\t\timageTensor.dispose();\n\n\t\t\t\t\t// Check predictions\n\t\t\t\t\tconst nsfwThreshold = this.config.nsfwThreshold || 0.6;\n\t\t\t\t\tfor (const prediction of predictions) {\n\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t(prediction.className === 'Porn'\n\t\t\t\t\t\t\t\t|| prediction.className === 'Hentai'\n\t\t\t\t\t\t\t\t|| prediction.className === 'Sexy')\n\t\t\t\t\t\t\t&& prediction.probability > nsfwThreshold\n\t\t\t\t\t\t) {\n\t\t\t\t\t\t\tresults.push({\n\t\t\t\t\t\t\t\ttype: 'nsfw',\n\t\t\t\t\t\t\t\tfilename: attachment.filename || 'unknown',\n\t\t\t\t\t\t\t\tcategory: prediction.className,\n\t\t\t\t\t\t\t\tprobability: prediction.probability,\n\t\t\t\t\t\t\t\tdescription: `NSFW image detected: ${prediction.className} (${(prediction.probability * 100).toFixed(1)}%)`,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tdebug('NSFW detection error for attachment:', attachment.filename, error);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tdebug('NSFW detection error:', error);\n\t\t\t// Don't fail the whole scan if NSFW detection fails\n\t\t}\n\n\t\treturn results;\n\t}\n\n\t// Normalize language codes from 3-letter to 2-letter format\n\tnormalizeLanguageCode(code) {\n\t\tif (!code || typeof code !== 'string') {\n\t\t\treturn 'en';\n\t\t}\n\n\t\t// If already 2-letter code, return as-is\n\t\tif (code.length === 2) {\n\t\t\treturn code.toLowerCase();\n\t\t}\n\n\t\t// Convert 3-letter ISO 639-2/3 codes to 2-letter ISO 639-1 codes\n\t\tconst codeMap = {\n\t\t\t// Common language mappings\n\t\t\teng: 'en', // English\n\t\t\tfra: 'fr', // French\n\t\t\tfre: 'fr', // French (alternative)\n\t\t\tspa: 'es', // Spanish\n\t\t\tdeu: 'de', // German\n\t\t\tger: 'de', // German (alternative)\n\t\t\tita: 'it', // Italian\n\t\t\tpor: 'pt', // Portuguese\n\t\t\trus: 'ru', // Russian\n\t\t\tjpn: 'ja', // Japanese\n\t\t\tkor: 'ko', // Korean\n\t\t\tcmn: 'zh', // Chinese (Mandarin)\n\t\t\tzho: 'zh', // Chinese\n\t\t\tchi: 'zh', // Chinese (alternative)\n\t\t\tara: 'ar', // Arabic\n\t\t\thin: 'hi', // Hindi\n\t\t\tben: 'bn', // Bengali\n\t\t\turd: 'ur', // Urdu\n\t\t\ttur: 'tr', // Turkish\n\t\t\tpol: 'pl', // Polish\n\t\t\tnld: 'nl', // Dutch\n\t\t\tdut: 'nl', // Dutch (alternative)\n\t\t\tswe: 'sv', // Swedish\n\t\t\tnor: 'no', // Norwegian\n\t\t\tdan: 'da', // Danish\n\t\t\tfin: 'fi', // Finnish\n\t\t\thun: 'hu', // Hungarian\n\t\t\tces: 'cs', // Czech\n\t\t\tcze: 'cs', // Czech (alternative)\n\t\t\tslk: 'sk', // Slovak\n\t\t\tslo: 'sk', // Slovak (alternative)\n\t\t\tslv: 'sl', // Slovenian\n\t\t\thrv: 'hr', // Croatian\n\t\t\tsrp: 'sr', // Serbian\n\t\t\tbul: 'bg', // Bulgarian\n\t\t\tron: 'ro', // Romanian\n\t\t\trum: 'ro', // Romanian (alternative)\n\t\t\tell: 'el', // Greek\n\t\t\tgre: 'el', // Greek (alternative)\n\t\t\theb: 'he', // Hebrew\n\t\t\ttha: 'th', // Thai\n\t\t\tvie: 'vi', // Vietnamese\n\t\t\tind: 'id', // Indonesian\n\t\t\tmsa: 'ms', // Malay\n\t\t\tmay: 'ms', // Malay (alternative)\n\t\t\ttgl: 'tl', // Tagalog\n\t\t\tukr: 'uk', // Ukrainian\n\t\t\tbel: 'be', // Belarusian\n\t\t\tlit: 'lt', // Lithuanian\n\t\t\tlav: 'lv', // Latvian\n\t\t\test: 'et', // Estonian\n\t\t\tcat: 'ca', // Catalan\n\t\t\teus: 'eu', // Basque\n\t\t\tbaq: 'eu', // Basque (alternative)\n\t\t\tglg: 'gl', // Galician\n\t\t\tgle: 'ga', // Irish\n\t\t\tgla: 'gd', // Scottish Gaelic\n\t\t\tcym: 'cy', // Welsh\n\t\t\twel: 'cy', // Welsh (alternative)\n\t\t\tisl: 'is', // Icelandic\n\t\t\tice: 'is', // Icelandic (alternative)\n\t\t\tmlt: 'mt', // Maltese\n\t\t\tafr: 'af', // Afrikaans\n\t\t\tswa: 'sw', // Swahili\n\t\t\tamh: 'am', // Amharic\n\t\t\thau: 'ha', // Hausa\n\t\t\tyor: 'yo', // Yoruba\n\t\t\tibo: 'ig', // Igbo\n\t\t\tsom: 'so', // Somali\n\t\t\torm: 'om', // Oromo\n\t\t\ttig: 'ti', // Tigrinya\n\t\t\tmlg: 'mg', // Malagasy\n\t\t\tnya: 'ny', // Chichewa\n\t\t\tsna: 'sn', // Shona\n\t\t\txho: 'xh', // Xhosa\n\t\t\tzul: 'zu', // Zulu\n\t\t\tnso: 'nso', // Northern Sotho\n\t\t\tsot: 'st', // Southern Sotho\n\t\t\ttsn: 'tn', // Tswana\n\t\t\tven: 've', // Venda\n\t\t\ttso: 'ts', // Tsonga\n\t\t\tssw: 'ss', // Swati\n\t\t\tnde: 'nr', // Southern Ndebele\n\t\t\tnbl: 'nd', // Northern Ndebele\n\t\t};\n\n\t\tconst normalized = code.toLowerCase();\n\t\treturn codeMap[normalized] || 'en';\n\t}\n}\n\nexport default SpamScanner;\n", "/**\n * Email Authentication Module\n * Integrates mailauth for DKIM, SPF, ARC, DMARC, BIMI checking\n */\n\nimport {Buffer} from 'node:buffer';\nimport {debuglog} from 'node:util';\nimport dns from 'node:dns';\n\nconst debug = debuglog('spamscanner:auth');\n\n// Lazy load mailauth to avoid issues if not installed\nlet mailauth;\nconst getMailauth = async () => {\n\tmailauth ||= await import('mailauth');\n\n\treturn mailauth;\n};\n\n/**\n * @typedef {Object} AuthResult\n * @property {Object} dkim - DKIM verification results\n * @property {Object} spf - SPF verification results\n * @property {Object} dmarc - DMARC verification results\n * @property {Object} arc - ARC verification results\n * @property {Object} bimi - BIMI verification results\n * @property {Array} receivedChain - Received header chain analysis\n * @property {Object} headers - Parsed authentication headers\n */\n\n/**\n * @typedef {Object} AuthOptions\n * @property {string} ip - Remote IP address of the sender\n * @property {string} [helo] - HELO/EHLO hostname\n * @property {string} [mta] - MTA hostname (for ARC sealing)\n * @property {string} [sender] - Envelope sender (MAIL FROM)\n * @property {Function} [resolver] - Custom DNS resolver function\n * @property {number} [timeout] - DNS lookup timeout in ms\n */\n\n/**\n * Default DNS resolver with timeout support\n */\nconst createResolver = (timeout = 10_000) => {\n\tconst resolver = new dns.promises.Resolver();\n\tresolver.setServers(['8.8.8.8', '1.1.1.1']);\n\n\treturn async (name, type) => {\n\t\ttry {\n\t\t\tconst controller = new AbortController();\n\t\t\tconst timeoutId = setTimeout(() => controller.abort(), timeout);\n\n\t\t\tlet result;\n\t\t\tswitch (type) {\n\t\t\t\tcase 'TXT': {\n\t\t\t\t\tresult = await resolver.resolveTxt(name);\n\t\t\t\t\t// Flatten TXT records (they come as arrays of strings)\n\t\t\t\t\tresult = result.map(r => (Array.isArray(r) ? r.join('') : r));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'MX': {\n\t\t\t\t\tresult = await resolver.resolveMx(name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'A': {\n\t\t\t\t\tresult = await resolver.resolve4(name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'AAAA': {\n\t\t\t\t\tresult = await resolver.resolve6(name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'PTR': {\n\t\t\t\t\tresult = await resolver.resolvePtr(name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase 'CNAME': {\n\t\t\t\t\tresult = await resolver.resolveCname(name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tdefault: {\n\t\t\t\t\tresult = await resolver.resolve(name, type);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tclearTimeout(timeoutId);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\tdebug('DNS lookup failed for %s %s: %s', type, name, error.message);\n\t\t\tthrow error;\n\t\t}\n\t};\n};\n\n/**\n * Authenticate an email message\n * @param {Buffer|string} message - Raw email message\n * @param {AuthOptions} options - Authentication options\n * @returns {Promise<AuthResult>}\n */\nasync function authenticate(message, options = {}) {\n\tconst {\n\t\tip,\n\t\thelo,\n\t\tmta,\n\t\tsender,\n\t\tresolver = createResolver(options.timeout || 10_000),\n\t} = options;\n\n\t// Default result structure\n\tconst defaultResult = {\n\t\tdkim: {\n\t\t\tresults: [],\n\t\t\tstatus: {result: 'none', comment: 'No DKIM signature found'},\n\t\t},\n\t\tspf: {\n\t\t\tstatus: {result: 'none', comment: 'SPF check not performed'},\n\t\t\tdomain: null,\n\t\t},\n\t\tdmarc: {\n\t\t\tstatus: {result: 'none', comment: 'DMARC check not performed'},\n\t\t\tpolicy: null,\n\t\t\tdomain: null,\n\t\t},\n\t\tarc: {\n\t\t\tstatus: {result: 'none', comment: 'No ARC chain found'},\n\t\t\tchain: [],\n\t\t},\n\t\tbimi: {\n\t\t\tstatus: {result: 'none', comment: 'No BIMI record found'},\n\t\t\tlocation: null,\n\t\t\tauthority: null,\n\t\t},\n\t\treceivedChain: [],\n\t\theaders: {},\n\t};\n\n\tif (!ip) {\n\t\tdebug('No IP address provided, skipping authentication');\n\t\treturn defaultResult;\n\t}\n\n\ttry {\n\t\tconst {authenticate: mailauthAuthenticate} = await getMailauth();\n\n\t\t// Convert string to Buffer if needed\n\t\tconst messageBuffer = Buffer.isBuffer(message) ? message : Buffer.from(message);\n\n\t\tconst authResult = await mailauthAuthenticate(messageBuffer, {\n\t\t\tip,\n\t\t\thelo: helo || 'unknown',\n\t\t\tmta: mta || 'spamscanner',\n\t\t\tsender,\n\t\t\tresolver,\n\t\t});\n\n\t\tdebug('Authentication result: %o', authResult);\n\n\t\t// Normalize the result\n\t\treturn {\n\t\t\tdkim: normalizeResult(authResult.dkim, 'dkim'),\n\t\t\tspf: normalizeResult(authResult.spf, 'spf'),\n\t\t\tdmarc: normalizeResult(authResult.dmarc, 'dmarc'),\n\t\t\tarc: normalizeResult(authResult.arc, 'arc'),\n\t\t\tbimi: normalizeResult(authResult.bimi, 'bimi'),\n\t\t\treceivedChain: authResult.receivedChain || [],\n\t\t\theaders: authResult.headers || {},\n\t\t};\n\t} catch (error) {\n\t\tdebug('Authentication failed: %s', error.message);\n\t\treturn defaultResult;\n\t}\n}\n\n/**\n * Perform SPF check only\n * @param {string} ip - Remote IP address\n * @param {string} sender - Envelope sender (MAIL FROM)\n * @param {string} [helo] - HELO/EHLO hostname\n * @param {Object} [options] - Additional options\n * @returns {Promise<Object>}\n */\nasync function checkSpf(ip, sender, helo, options = {}) {\n\tconst {\n\t\tresolver = createResolver(options.timeout || 10_000),\n\t\tmta = 'spamscanner',\n\t} = options;\n\n\tconst defaultResult = {\n\t\tstatus: {result: 'none', comment: 'SPF check not performed'},\n\t\tdomain: null,\n\t};\n\n\tif (!ip || !sender) {\n\t\treturn defaultResult;\n\t}\n\n\ttry {\n\t\tconst {spf} = await getMailauth();\n\n\t\tconst result = await spf({\n\t\t\tip,\n\t\t\tsender,\n\t\t\thelo: helo || 'unknown',\n\t\t\tmta,\n\t\t\tresolver,\n\t\t});\n\n\t\treturn normalizeResult(result, 'spf');\n\t} catch (error) {\n\t\tdebug('SPF check failed: %s', error.message);\n\t\treturn defaultResult;\n\t}\n}\n\n/**\n * Verify DKIM signature\n * @param {Buffer|string} message - Raw email message\n * @param {Object} [options] - Additional options\n * @returns {Promise<Object>}\n */\nasync function verifyDkim(message, options = {}) {\n\tconst {\n\t\tresolver = createResolver(options.timeout || 10_000),\n\t} = options;\n\n\tconst defaultResult = {\n\t\tresults: [],\n\t\tstatus: {result: 'none', comment: 'No DKIM signature found'},\n\t};\n\n\ttry {\n\t\tconst {dkimVerify} = await getMailauth();\n\n\t\tconst messageBuffer = Buffer.isBuffer(message) ? message : Buffer.from(message);\n\n\t\tconst result = await dkimVerify(messageBuffer, {\n\t\t\tresolver,\n\t\t});\n\n\t\treturn normalizeResult(result, 'dkim');\n\t} catch (error) {\n\t\tdebug('DKIM verification failed: %s', error.message);\n\t\treturn defaultResult;\n\t}\n}\n\n/**\n * Normalize authentication result to consistent structure\n * @param {Object} result - Raw result from mailauth\n * @param {string} type - Type of authentication (dkim, spf, dmarc, arc, bimi)\n * @returns {Object}\n */\nfunction normalizeResult(result, type) {\n\tif (!result) {\n\t\treturn {\n\t\t\tstatus: {result: 'none', comment: `No ${type.toUpperCase()} result`},\n\t\t};\n\t}\n\n\tswitch (type) {\n\t\tcase 'dkim': {\n\t\t\treturn {\n\t\t\t\tresults: result.results || [],\n\t\t\t\tstatus: result.status || {result: 'none', comment: 'No DKIM signature found'},\n\t\t\t};\n\t\t}\n\n\t\tcase 'spf': {\n\t\t\treturn {\n\t\t\t\tstatus: result.status || {result: 'none', comment: 'SPF check not performed'},\n\t\t\t\tdomain: result.domain || null,\n\t\t\t\texplanation: result.explanation || null,\n\t\t\t};\n\t\t}\n\n\t\tcase 'dmarc': {\n\t\t\treturn {\n\t\t\t\tstatus: result.status || {result: 'none', comment: 'DMARC check not performed'},\n\t\t\t\tpolicy: result.policy || null,\n\t\t\t\tdomain: result.domain || null,\n\t\t\t\tp: result.p || null,\n\t\t\t\tsp: result.sp || null,\n\t\t\t\tpct: result.pct || null,\n\t\t\t};\n\t\t}\n\n\t\tcase 'arc': {\n\t\t\treturn {\n\t\t\t\tstatus: result.status || {result: 'none', comment: 'No ARC chain found'},\n\t\t\t\tchain: result.chain || [],\n\t\t\t\ti: result.i || null,\n\t\t\t};\n\t\t}\n\n\t\tcase 'bimi': {\n\t\t\treturn {\n\t\t\t\tstatus: result.status || {result: 'none', comment: 'No BIMI record found'},\n\t\t\t\tlocation: result.location || null,\n\t\t\t\tauthority: result.authority || null,\n\t\t\t\tselector: result.selector || null,\n\t\t\t};\n\t\t}\n\n\t\tdefault: {\n\t\t\treturn result;\n\t\t}\n\t}\n}\n\n/**\n * Calculate authentication score based on results\n * @param {AuthResult} authResult - Authentication results\n * @param {Object} weights - Score weights for each check\n * @returns {Object} Score breakdown\n */\nfunction calculateAuthScore(authResult, weights = {}) {\n\tconst defaultWeights = {\n\t\tdkimPass: -2, // Reduce spam score if DKIM passes\n\t\tdkimFail: 3, // Increase spam score if DKIM fails\n\t\tspfPass: -1,\n\t\tspfFail: 2,\n\t\tspfSoftfail: 1,\n\t\tdmarcPass: -2,\n\t\tdmarcFail: 4,\n\t\tarcPass: -1,\n\t\tarcFail: 1,\n\t\t...weights,\n\t};\n\n\tlet score = 0;\n\tconst tests = [];\n\n\t// DKIM scoring\n\tconst dkimResult = authResult.dkim?.status?.result;\n\tif (dkimResult === 'pass') {\n\t\tscore += defaultWeights.dkimPass;\n\t\ttests.push(`DKIM_PASS(${defaultWeights.dkimPass})`);\n\t} else if (dkimResult === 'fail') {\n\t\tscore += defaultWeights.dkimFail;\n\t\ttests.push(`DKIM_FAIL(${defaultWeights.dkimFail})`);\n\t}\n\n\t// SPF scoring\n\tconst spfResult = authResult.spf?.status?.result;\n\tswitch (spfResult) {\n\t\tcase 'pass': {\n\t\t\tscore += defaultWeights.spfPass;\n\t\t\ttests.push(`SPF_PASS(${defaultWeights.spfPass})`);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'fail': {\n\t\t\tscore += defaultWeights.spfFail;\n\t\t\ttests.push(`SPF_FAIL(${defaultWeights.spfFail})`);\n\n\t\t\tbreak;\n\t\t}\n\n\t\tcase 'softfail': {\n\t\t\tscore += defaultWeights.spfSoftfail;\n\t\t\ttests.push(`SPF_SOFTFAIL(${defaultWeights.spfSoftfail})`);\n\n\t\t\tbreak;\n\t\t}\n\t// No default\n\t}\n\n\t// DMARC scoring\n\tconst dmarcResult = authResult.dmarc?.status?.result;\n\tif (dmarcResult === 'pass') {\n\t\tscore += defaultWeights.dmarcPass;\n\t\ttests.push(`DMARC_PASS(${defaultWeights.dmarcPass})`);\n\t} else if (dmarcResult === 'fail') {\n\t\tscore += defaultWeights.dmarcFail;\n\t\ttests.push(`DMARC_FAIL(${defaultWeights.dmarcFail})`);\n\t}\n\n\t// ARC scoring\n\tconst arcResult = authResult.arc?.status?.result;\n\tif (arcResult === 'pass') {\n\t\tscore += defaultWeights.arcPass;\n\t\ttests.push(`ARC_PASS(${defaultWeights.arcPass})`);\n\t} else if (arcResult === 'fail') {\n\t\tscore += defaultWeights.arcFail;\n\t\ttests.push(`ARC_FAIL(${defaultWeights.arcFail})`);\n\t}\n\n\treturn {\n\t\tscore,\n\t\ttests,\n\t\tdetails: {\n\t\t\tdkim: dkimResult || 'none',\n\t\t\tspf: spfResult || 'none',\n\t\t\tdmarc: dmarcResult || 'none',\n\t\t\tarc: arcResult || 'none',\n\t\t},\n\t};\n}\n\n/**\n * Format authentication results as Authentication-Results header\n * @param {AuthResult} authResult - Authentication results\n * @param {string} hostname - MTA hostname\n * @returns {string}\n */\nfunction formatAuthResultsHeader(authResult, hostname = 'spamscanner') {\n\tconst parts = [hostname];\n\n\t// DKIM\n\tif (authResult.dkim?.status?.result) {\n\t\tconst dkimResult = authResult.dkim.status.result;\n\t\tlet dkimPart = `dkim=${dkimResult}`;\n\t\tif (authResult.dkim.results?.[0]?.signingDomain) {\n\t\t\tdkimPart += ` header.d=${authResult.dkim.results[0].signingDomain}`;\n\t\t}\n\n\t\tparts.push(dkimPart);\n\t}\n\n\t// SPF\n\tif (authResult.spf?.status?.result) {\n\t\tlet spfPart = `spf=${authResult.spf.status.result}`;\n\t\tif (authResult.spf.domain) {\n\t\t\tspfPart += ` smtp.mailfrom=${authResult.spf.domain}`;\n\t\t}\n\n\t\tparts.push(spfPart);\n\t}\n\n\t// DMARC\n\tif (authResult.dmarc?.status?.result) {\n\t\tlet dmarcPart = `dmarc=${authResult.dmarc.status.result}`;\n\t\tif (authResult.dmarc.domain) {\n\t\t\tdmarcPart += ` header.from=${authResult.dmarc.domain}`;\n\t\t}\n\n\t\tparts.push(dmarcPart);\n\t}\n\n\t// ARC\n\tif (authResult.arc?.status?.result) {\n\t\tparts.push(`arc=${authResult.arc.status.result}`);\n\t}\n\n\treturn parts.join(';\\n\\t');\n}\n\nexport {\n\tauthenticate,\n\tcheckSpf,\n\tverifyDkim,\n\tcalculateAuthScore,\n\tformatAuthResultsHeader,\n\tcreateResolver,\n};\n", "/**\n * Forward Email Reputation API Client\n * Checks IP addresses, domains, and emails against Forward Email's reputation database\n */\n\nimport {debuglog} from 'node:util';\n\nconst debug = debuglog('spamscanner:reputation');\n\n// Default Forward Email API URL\nconst DEFAULT_API_URL = 'https://api.forwardemail.net/v1/reputation';\n\n// Cache for reputation results (TTL: 5 minutes)\nconst cache = new Map();\nconst CACHE_TTL = 5 * 60 * 1000; // 5 minutes\n\n/**\n * @typedef {Object} ReputationResult\n * @property {boolean} isTruthSource - Whether the sender is a known truth source\n * @property {string|null} truthSourceValue - The truth source entry that matched\n * @property {boolean} isAllowlisted - Whether the sender is allowlisted\n * @property {string|null} allowlistValue - The allowlist entry that matched\n * @property {boolean} isDenylisted - Whether the sender is denylisted\n * @property {string|null} denylistValue - The denylist entry that matched\n */\n\n/**\n * Check reputation for a single value (IP, domain, or email)\n * @param {string} value - The value to check\n * @param {Object} options - Options\n * @param {string} [options.apiUrl] - Custom API URL\n * @param {number} [options.timeout] - Request timeout in ms\n * @returns {Promise<ReputationResult>}\n */\nasync function checkReputation(value, options = {}) {\n\tconst {\n\t\tapiUrl = DEFAULT_API_URL,\n\t\ttimeout = 10_000,\n\t} = options;\n\n\tif (!value || typeof value !== 'string') {\n\t\treturn {\n\t\t\tisTruthSource: false,\n\t\t\ttruthSourceValue: null,\n\t\t\tisAllowlisted: false,\n\t\t\tallowlistValue: null,\n\t\t\tisDenylisted: false,\n\t\t\tdenylistValue: null,\n\t\t};\n\t}\n\n\t// Check cache first\n\tconst cacheKey = `${apiUrl}:${value}`;\n\tconst cached = cache.get(cacheKey);\n\tif (cached && Date.now() - cached.timestamp < CACHE_TTL) {\n\t\tdebug('Cache hit for %s', value);\n\t\treturn cached.result;\n\t}\n\n\ttry {\n\t\tconst url = new URL(apiUrl);\n\t\turl.searchParams.set('q', value);\n\n\t\tconst controller = new AbortController();\n\t\tconst timeoutId = setTimeout(() => controller.abort(), timeout);\n\n\t\tconst response = await fetch(url.toString(), {\n\t\t\tmethod: 'GET',\n\t\t\theaders: {\n\t\t\t\tAccept: 'application/json',\n\t\t\t\t'User-Agent': 'SpamScanner/6.0',\n\t\t\t},\n\t\t\tsignal: controller.signal,\n\t\t});\n\n\t\tclearTimeout(timeoutId);\n\n\t\tif (!response.ok) {\n\t\t\tdebug('API returned status %d for %s', response.status, value);\n\t\t\t// Return default values on error\n\t\t\treturn {\n\t\t\t\tisTruthSource: false,\n\t\t\t\ttruthSourceValue: null,\n\t\t\t\tisAllowlisted: false,\n\t\t\t\tallowlistValue: null,\n\t\t\t\tisDenylisted: false,\n\t\t\t\tdenylistValue: null,\n\t\t\t};\n\t\t}\n\n\t\tconst result = await response.json();\n\n\t\t// Normalize the result\n\t\tconst normalizedResult = {\n\t\t\tisTruthSource: Boolean(result.isTruthSource),\n\t\t\ttruthSourceValue: result.truthSourceValue || null,\n\t\t\tisAllowlisted: Boolean(result.isAllowlisted),\n\t\t\tallowlistValue: result.allowlistValue || null,\n\t\t\tisDenylisted: Boolean(result.isDenylisted),\n\t\t\tdenylistValue: result.denylistValue || null,\n\t\t};\n\n\t\t// Cache the result\n\t\tcache.set(cacheKey, {\n\t\t\tresult: normalizedResult,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tdebug('Reputation check for %s: %o', value, normalizedResult);\n\t\treturn normalizedResult;\n\t} catch (error) {\n\t\tdebug('Reputation check failed for %s: %s', value, error.message);\n\t\t// Return default values on error\n\t\treturn {\n\t\t\tisTruthSource: false,\n\t\t\ttruthSourceValue: null,\n\t\t\tisAllowlisted: false,\n\t\t\tallowlistValue: null,\n\t\t\tisDenylisted: false,\n\t\t\tdenylistValue: null,\n\t\t};\n\t}\n}\n\n/**\n * Check reputation for multiple values in parallel\n * @param {string[]} values - Array of values to check (IPs, domains, emails)\n * @param {Object} options - Options\n * @returns {Promise<Map<string, ReputationResult>>}\n */\nasync function checkReputationBatch(values, options = {}) {\n\tconst uniqueValues = [...new Set(values.filter(Boolean))];\n\n\tconst results = await Promise.all(uniqueValues.map(async value => {\n\t\tconst result = await checkReputation(value, options);\n\t\treturn [value, result];\n\t}));\n\n\treturn new Map(results);\n}\n\n/**\n * Aggregate reputation results from multiple checks\n * @param {ReputationResult[]} results - Array of reputation results\n * @returns {ReputationResult}\n */\nfunction aggregateReputationResults(results) {\n\tconst aggregated = {\n\t\tisTruthSource: false,\n\t\ttruthSourceValue: null,\n\t\tisAllowlisted: false,\n\t\tallowlistValue: null,\n\t\tisDenylisted: false,\n\t\tdenylistValue: null,\n\t};\n\n\tfor (const result of results) {\n\t\t// Any truth source match is a truth source\n\t\tif (result.isTruthSource) {\n\t\t\taggregated.isTruthSource = true;\n\t\t\taggregated.truthSourceValue ||= result.truthSourceValue;\n\t\t}\n\n\t\t// Any allowlist match is allowlisted\n\t\tif (result.isAllowlisted) {\n\t\t\taggregated.isAllowlisted = true;\n\t\t\taggregated.allowlistValue ||= result.allowlistValue;\n\t\t}\n\n\t\t// Any denylist match is denylisted (takes precedence)\n\t\tif (result.isDenylisted) {\n\t\t\taggregated.isDenylisted = true;\n\t\t\taggregated.denylistValue ||= result.denylistValue;\n\t\t}\n\t}\n\n\treturn aggregated;\n}\n\n/**\n * Clear the reputation cache\n */\nfunction clearCache() {\n\tcache.clear();\n}\n\nexport {\n\tcheckReputation,\n\tcheckReputationBatch,\n\taggregateReputationResults,\n\tclearCache,\n\tDEFAULT_API_URL,\n};\n", "/**\n * Arbitrary Spam Detection Module\n * Based on Forward Email's is-arbitrary helper\n * Detects common spam patterns, Microsoft Exchange spam, and arbitrary spam indicators\n *\n * @see https://github.com/forwardemail/forwardemail.net/blob/master/helpers/is-arbitrary.js\n */\n\nimport {debuglog} from 'node:util';\n\nconst debug = debuglog('spamscanner:arbitrary');\n\n/**\n * @typedef {Object} ArbitraryResult\n * @property {boolean} isArbitrary - Whether the message appears to be arbitrary spam\n * @property {string[]} reasons - List of reasons why the message was flagged\n * @property {number} score - Arbitrary spam score\n * @property {string|null} category - Spam category if detected (e.g., 'PHISHING', 'MALWARE', 'SPOOFING')\n */\n\n/**\n * @typedef {Object} SessionInfo\n * @property {string} [resolvedClientHostname] - Resolved hostname of connecting client\n * @property {string} [resolvedRootClientHostname] - Root domain of resolved client hostname\n * @property {string} [remoteAddress] - IP address of connecting client\n * @property {string} [originalFromAddress] - Email address from From header\n * @property {string} [originalFromAddressDomain] - Domain from From header\n * @property {string} [originalFromAddressRootDomain] - Root domain from From header\n * @property {Object} [envelope] - SMTP envelope\n * @property {Object} [envelope.mailFrom] - MAIL FROM address\n * @property {Array} [envelope.rcptTo] - RCPT TO addresses\n * @property {boolean} [hadAlignedAndPassingDKIM] - Whether DKIM was aligned and passing\n * @property {Object} [spfFromHeader] - SPF result for From header\n * @property {boolean} [hasSameHostnameAsFrom] - Whether client hostname matches From domain\n * @property {boolean} [isAllowlisted] - Whether sender is allowlisted\n * @property {Set} [signingDomains] - Set of DKIM signing domains\n */\n\n// Blocked phrases that indicate obvious spam\nconst BLOCKED_PHRASES_PATTERN\n\t= /cheecck y0ur acc0untt|recorded you|you've been hacked|account is hacked|personal data has leaked|private information has been stolen/im;\n\n// Sysadmin subject patterns (legitimate automated emails)\nconst SYSADMIN_SUBJECT_PATTERN\n\t= /please moderate|mdadm monitoring|weekly report|wordfence|wordpress|wpforms|docker|graylog|digest|event notification|package update manager|event alert|system events|monit alert|ping|monitor|cron|yum|sendmail|exim|backup|logwatch|unattended-upgrades/im;\n\n// Common spam patterns and indicators\nconst SPAM_PATTERNS = {\n\t// Subject line patterns\n\tsubjectPatterns: [\n\t\t// Urgency patterns\n\t\t/\\b(urgent|immediate|action required|act now|limited time|expires?|deadline)\\b/i,\n\t\t// Money patterns\n\t\t/\\b(free|winner|won|prize|lottery|million|billion|cash|money|investment|profit)\\b/i,\n\t\t// Phishing patterns\n\t\t/\\b(verify|confirm|update|suspend|locked|unusual activity|security alert)\\b/i,\n\t\t// Adult content\n\t\t/\\b(viagra|cialis|pharmacy|pills|medication|prescription)\\b/i,\n\t\t// Crypto spam\n\t\t/\\b(bitcoin|crypto|btc|eth|nft|blockchain|wallet)\\b/i,\n\t],\n\n\t// Body patterns\n\tbodyPatterns: [\n\t\t// Nigerian prince / advance fee fraud\n\t\t/\\b(nigerian?|prince|inheritance|beneficiary|next of kin|deceased|unclaimed)\\b/i,\n\t\t// Lottery scams\n\t\t/\\b(congratulations.*won|you have been selected|claim your prize)\\b/i,\n\t\t// Phishing\n\t\t/\\b(click here to verify|confirm your identity|update your account|suspended.*account)\\b/i,\n\t\t// Urgency\n\t\t/\\b(act now|limited time offer|expires in \\d+|only \\d+ left)\\b/i,\n\t\t// Financial scams\n\t\t/\\b(wire transfer|western union|moneygram|bank transfer|routing number)\\b/i,\n\t\t// Adult/pharma spam\n\t\t/\\b(enlarge|enhancement|erectile|dysfunction|weight loss|diet pills)\\b/i,\n\t],\n\n\t// Suspicious sender patterns\n\tsenderPatterns: [\n\t\t// Random numbers in email\n\t\t/^[a-z]+\\d{4,}@/i,\n\t\t// Very long local parts\n\t\t/^.{30,}@/,\n\t\t// Suspicious domains\n\t\t/@.*(\\.ru|\\.cn|\\.tk|\\.ml|\\.ga|\\.cf|\\.gq)$/i,\n\t\t// Numeric domains\n\t\t/@(?:\\d+\\.){3}\\d+/,\n\t],\n};\n\n// Suspicious TLDs commonly used in spam\nconst SUSPICIOUS_TLDS = new Set([\n\t'tk',\n\t'ml',\n\t'ga',\n\t'cf',\n\t'gq', // Free TLDs often abused\n\t'xyz',\n\t'top',\n\t'wang',\n\t'win',\n\t'bid',\n\t'loan',\n\t'click',\n\t'link',\n\t'work',\n\t'date',\n\t'racing',\n\t'download',\n\t'stream',\n\t'trade',\n]);\n\n// Common spam keywords with weights\nconst SPAM_KEYWORDS = new Map([\n\t['free', 1],\n\t['winner', 2],\n\t['prize', 2],\n\t['lottery', 3],\n\t['urgent', 1],\n\t['act now', 2],\n\t['limited time', 1],\n\t['click here', 1],\n\t['unsubscribe', -1], // Legitimate emails often have this\n\t['verify your account', 2],\n\t['suspended', 2],\n\t['inheritance', 3],\n\t['million dollars', 3],\n\t['wire transfer', 3],\n\t['western union', 3],\n\t['nigerian', 3],\n\t['prince', 2],\n\t['beneficiary', 2],\n\t['congratulations', 1],\n\t['selected', 1],\n\t['viagra', 3],\n\t['cialis', 3],\n\t['pharmacy', 2],\n\t['bitcoin', 1],\n\t['crypto', 1],\n\t['investment opportunity', 2],\n\t['guaranteed', 1],\n\t['risk free', 2],\n\t['no obligation', 1],\n\t['dear friend', 2],\n\t['dear customer', 1],\n\t['dear user', 1],\n]);\n\n// PayPal spam email type IDs\nconst PAYPAL_SPAM_TYPE_IDS = new Set(['PPC001017', 'RT000238', 'RT000542', 'RT002947']);\n\n/**\n * Microsoft Exchange Spam Categories (CAT values)\n * @see https://learn.microsoft.com/en-us/defender-office-365/how-policies-and-protections-are-combined\n */\nconst MS_SPAM_CATEGORIES = {\n\t// High-confidence threats (highest priority)\n\thighConfidence: ['cat:malw', 'cat:hphsh', 'cat:hphish', 'cat:hspm'],\n\t// Impersonation attempts\n\timpersonation: ['cat:bimp', 'cat:dimp', 'cat:gimp', 'cat:uimp'],\n\t// Phishing and spoofing\n\tphishingAndSpoofing: ['cat:phsh', 'cat:spoof'],\n\t// Spam classifications\n\tspam: ['cat:ospm', 'cat:spm'],\n};\n\n/**\n * Microsoft Spam Filtering Verdicts (SFV values)\n * @see https://learn.microsoft.com/en-us/defender-office-365/message-headers-eop-mdo\n */\nconst MS_SPAM_VERDICTS = ['sfv:spm', 'sfv:skb', 'sfv:sks'];\n\n/**\n * Check if a message appears to be arbitrary spam\n * @param {Object} parsed - Parsed email message (from mailparser)\n * @param {Object} options - Detection options\n * @param {SessionInfo} [options.session] - Session information for advanced checks\n * @returns {ArbitraryResult}\n */\nfunction isArbitrary(parsed, options = {}) {\n\tconst {\n\t\tthreshold = 5,\n\t\tcheckSubject = true,\n\t\tcheckBody = true,\n\t\tcheckSender = true,\n\t\tcheckHeaders = true,\n\t\tcheckLinks = true,\n\t\tcheckMicrosoftHeaders = true,\n\t\tcheckVendorSpam = true,\n\t\tcheckSpoofing = true,\n\t\tsession = {},\n\t} = options;\n\n\tconst reasons = [];\n\tlet score = 0;\n\tlet category = null;\n\n\t// Get headers helper\n\tconst getHeader = name => {\n\t\tif (parsed.headers?.get) {\n\t\t\treturn parsed.headers.get(name);\n\t\t}\n\n\t\tif (parsed.headerLines) {\n\t\t\tconst header = parsed.headerLines.find(h => h.key.toLowerCase() === name.toLowerCase());\n\t\t\treturn header?.line?.split(':').slice(1).join(':').trim();\n\t\t}\n\n\t\treturn null;\n\t};\n\n\tconst subject = parsed.subject || getHeader('subject') || '';\n\tconst from = parsed.from?.value?.[0]?.address || parsed.from?.text || getHeader('from') || '';\n\n\t// Build session info from parsed email if not provided\n\tconst sessionInfo = buildSessionInfo(parsed, session, getHeader);\n\n\t// Check for blocked phrases in subject\n\tif (subject && BLOCKED_PHRASES_PATTERN.test(subject)) {\n\t\treasons.push('BLOCKED_PHRASE_IN_SUBJECT');\n\t\tscore += 10;\n\t\tcategory = 'SPAM';\n\t}\n\n\t// Check Microsoft Exchange headers (only if from Microsoft infrastructure)\n\tif (checkMicrosoftHeaders) {\n\t\tconst msResult = checkMicrosoftExchangeHeaders(getHeader, sessionInfo);\n\t\tif (msResult.blocked) {\n\t\t\treasons.push(...msResult.reasons);\n\t\t\tscore += msResult.score;\n\t\t\tcategory = msResult.category || category;\n\t\t}\n\t}\n\n\t// Check vendor-specific spam patterns\n\tif (checkVendorSpam) {\n\t\tconst vendorResult = checkVendorSpam_(parsed, sessionInfo, getHeader, subject, from);\n\t\tif (vendorResult.blocked) {\n\t\t\treasons.push(...vendorResult.reasons);\n\t\t\tscore += vendorResult.score;\n\t\t\tcategory = vendorResult.category || category;\n\t\t}\n\t}\n\n\t// Check for spoofing attacks\n\tif (checkSpoofing) {\n\t\tconst spoofResult = checkSpoofingAttacks(parsed, sessionInfo, getHeader, subject);\n\t\tif (spoofResult.blocked) {\n\t\t\treasons.push(...spoofResult.reasons);\n\t\t\tscore += spoofResult.score;\n\t\t\tcategory = spoofResult.category || category;\n\t\t}\n\t}\n\n\t// Check subject line\n\tif (checkSubject && subject) {\n\t\tconst subjectResult = checkSubjectLine(subject);\n\t\tscore += subjectResult.score;\n\t\treasons.push(...subjectResult.reasons);\n\t}\n\n\t// Check body content\n\tif (checkBody) {\n\t\tconst bodyText = parsed.text || '';\n\t\tconst bodyHtml = parsed.html || '';\n\t\tconst bodyResult = checkBodyContent(bodyText, bodyHtml);\n\t\tscore += bodyResult.score;\n\t\treasons.push(...bodyResult.reasons);\n\t}\n\n\t// Check sender\n\tif (checkSender) {\n\t\tconst replyTo = parsed.replyTo?.value?.[0]?.address || parsed.replyTo?.text || '';\n\t\tconst senderResult = checkSenderPatterns(from, replyTo);\n\t\tscore += senderResult.score;\n\t\treasons.push(...senderResult.reasons);\n\t}\n\n\t// Check headers\n\tif (checkHeaders) {\n\t\tconst headerResult = checkHeaderAnomalies(parsed, getHeader);\n\t\tscore += headerResult.score;\n\t\treasons.push(...headerResult.reasons);\n\t}\n\n\t// Check links\n\tif (checkLinks) {\n\t\tconst bodyHtml = parsed.html || parsed.text || '';\n\t\tconst linkResult = checkSuspiciousLinks(bodyHtml);\n\t\tscore += linkResult.score;\n\t\treasons.push(...linkResult.reasons);\n\t}\n\n\tconst isArbitrarySpam = score >= threshold;\n\n\tdebug(\n\t\t'Arbitrary check result: score=%d, threshold=%d, isArbitrary=%s, category=%s, reasons=%o',\n\t\tscore,\n\t\tthreshold,\n\t\tisArbitrarySpam,\n\t\tcategory,\n\t\treasons,\n\t);\n\n\treturn {\n\t\tisArbitrary: isArbitrarySpam,\n\t\treasons,\n\t\tscore,\n\t\tcategory,\n\t};\n}\n\n/**\n * Build session info from parsed email and provided session\n * @param {Object} parsed - Parsed email\n * @param {SessionInfo} session - Provided session info\n * @param {Function} getHeader - Header getter function\n * @returns {SessionInfo}\n */\nfunction buildSessionInfo(parsed, session, getHeader) {\n\tconst info = {...session};\n\n\t// Extract from address info\n\tconst from = parsed.from?.value?.[0]?.address || parsed.from?.text || getHeader('from') || '';\n\tif (from && !info.originalFromAddress) {\n\t\tinfo.originalFromAddress = from.toLowerCase();\n\t\tconst atIndex = from.indexOf('@');\n\t\tif (atIndex > 0) {\n\t\t\tinfo.originalFromAddressDomain = from.slice(atIndex + 1).toLowerCase();\n\t\t\tinfo.originalFromAddressRootDomain = getRootDomain(info.originalFromAddressDomain);\n\t\t}\n\t}\n\n\t// Extract client hostname from Received headers\n\tif (!info.resolvedClientHostname) {\n\t\tinfo.resolvedClientHostname = extractClientHostname(parsed);\n\t\tif (info.resolvedClientHostname) {\n\t\t\tinfo.resolvedRootClientHostname = getRootDomain(info.resolvedClientHostname);\n\t\t}\n\t}\n\n\t// Extract remote IP\n\tinfo.remoteAddress ||= extractRemoteIp(parsed);\n\n\t// Only use envelope if provided from actual SMTP session\n\t// Don't create fake envelope from headers as it can cause false positives\n\t// in spoofing detection (To header != RCPT TO in SMTP)\n\n\treturn info;\n}\n\n/**\n * Check Microsoft Exchange headers for spam classification\n * This detects spam forwarded through Microsoft Exchange infrastructure\n *\n * @see https://learn.microsoft.com/en-us/defender-office-365/message-headers-eop-mdo\n * @param {Function} getHeader - Header getter function\n * @param {SessionInfo} sessionInfo - Session information\n * @returns {{blocked: boolean, reasons: string[], score: number, category: string|null}}\n */\nfunction checkMicrosoftExchangeHeaders(getHeader, sessionInfo) {\n\tconst result = {\n\t\tblocked: false, reasons: [], score: 0, category: null,\n\t};\n\n\t// Only check if message came from Microsoft infrastructure\n\tconst isFromMicrosoft\n\t\t= sessionInfo.resolvedClientHostname\n\t\t\t&& sessionInfo.resolvedClientHostname.endsWith('.outbound.protection.outlook.com');\n\n\tif (!isFromMicrosoft) {\n\t\treturn result;\n\t}\n\n\tconst msAuthHeader = getHeader('x-ms-exchange-authentication-results');\n\tconst forefrontHeader = getHeader('x-forefront-antispam-report');\n\n\t// Check authentication failures first (if Microsoft didn't mark as non-spam)\n\tif (forefrontHeader) {\n\t\tconst lowerForefront = forefrontHeader.toLowerCase();\n\n\t\t// Extract SCL (Spam Confidence Level)\n\t\tconst sclMatch = lowerForefront.match(/scl:(\\d+)/);\n\t\tconst scl = sclMatch ? Number.parseInt(sclMatch[1], 10) : null;\n\n\t\t// Check if Microsoft says it's NOT spam\n\t\tconst sfvNotSpam = lowerForefront.includes('sfv:nspm');\n\t\tconst microsoftSaysNotSpam = sfvNotSpam || (scl !== null && scl <= 2);\n\n\t\t// Only check authentication if Microsoft didn't clear it\n\t\tif (!microsoftSaysNotSpam && msAuthHeader) {\n\t\t\tconst lowerMsAuth = msAuthHeader.toLowerCase();\n\n\t\t\t// Check if any authentication passed\n\t\t\tconst spfPass = lowerMsAuth.includes('spf=pass');\n\t\t\tconst dkimPass = lowerMsAuth.includes('dkim=pass');\n\t\t\tconst dmarcPass = lowerMsAuth.includes('dmarc=pass');\n\n\t\t\t// Only block if ALL authentication methods failed\n\t\t\tif (!spfPass && !dkimPass && !dmarcPass) {\n\t\t\t\t// Check for hard failures (not softfail)\n\t\t\t\tconst spfFailed = lowerMsAuth.includes('spf=fail');\n\t\t\t\tconst dkimFailed = lowerMsAuth.includes('dkim=fail');\n\t\t\t\tconst dmarcFailed = lowerMsAuth.includes('dmarc=fail');\n\n\t\t\t\tif (spfFailed || dkimFailed || dmarcFailed) {\n\t\t\t\t\tresult.blocked = true;\n\t\t\t\t\tresult.reasons.push('MS_EXCHANGE_AUTH_FAILURE');\n\t\t\t\t\tresult.score += 10;\n\t\t\t\t\tresult.category = 'AUTHENTICATION_FAILURE';\n\t\t\t\t\treturn result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Check for high-confidence threats\n\t\tfor (const cat of MS_SPAM_CATEGORIES.highConfidence) {\n\t\t\tif (lowerForefront.includes(cat)) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push(`MS_HIGH_CONFIDENCE_THREAT: ${cat.toUpperCase()}`);\n\t\t\t\tresult.score += 15;\n\t\t\t\tresult.category = cat.includes('malw')\n\t\t\t\t\t? 'MALWARE'\n\t\t\t\t\t: (cat.includes('phish') || cat.includes('phsh')\n\t\t\t\t\t\t? 'PHISHING'\n\t\t\t\t\t\t: 'HIGH_CONFIDENCE_SPAM');\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\n\t\t// Check for impersonation attempts\n\t\tfor (const cat of MS_SPAM_CATEGORIES.impersonation) {\n\t\t\tif (lowerForefront.includes(cat)) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push(`MS_IMPERSONATION: ${cat.toUpperCase()}`);\n\t\t\t\tresult.score += 12;\n\t\t\t\tresult.category = 'IMPERSONATION';\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\n\t\t// Check for phishing and spoofing\n\t\tfor (const cat of MS_SPAM_CATEGORIES.phishingAndSpoofing) {\n\t\t\tif (lowerForefront.includes(cat)) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push(`MS_PHISHING_SPOOF: ${cat.toUpperCase()}`);\n\t\t\t\tresult.score += 12;\n\t\t\t\tresult.category = cat.includes('phsh') ? 'PHISHING' : 'SPOOFING';\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\n\t\t// Check spam verdicts\n\t\tfor (const verdict of MS_SPAM_VERDICTS) {\n\t\t\tif (lowerForefront.includes(verdict)) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push(`MS_SPAM_VERDICT: ${verdict.toUpperCase()}`);\n\t\t\t\tresult.score += 10;\n\t\t\t\tresult.category = 'SPAM';\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\n\t\t// Check spam categories\n\t\tfor (const cat of MS_SPAM_CATEGORIES.spam) {\n\t\t\tif (lowerForefront.includes(cat)) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push(`MS_SPAM_CATEGORY: ${cat.toUpperCase()}`);\n\t\t\t\tresult.score += 10;\n\t\t\t\tresult.category = 'SPAM';\n\t\t\t\treturn result;\n\t\t\t}\n\t\t}\n\n\t\t// Check SCL threshold (5+ is spam)\n\t\tif (scl !== null && scl >= 5) {\n\t\t\tresult.blocked = true;\n\t\t\tresult.reasons.push(`MS_HIGH_SCL: ${scl}`);\n\t\t\tresult.score += 8;\n\t\t\tresult.category = 'SPAM';\n\t\t\treturn result;\n\t\t}\n\t} else if (msAuthHeader) {\n\t\t// No forefront header, check authentication only\n\t\tconst lowerMsAuth = msAuthHeader.toLowerCase();\n\n\t\tconst spfPass = lowerMsAuth.includes('spf=pass');\n\t\tconst dkimPass = lowerMsAuth.includes('dkim=pass');\n\t\tconst dmarcPass = lowerMsAuth.includes('dmarc=pass');\n\n\t\tif (!spfPass && !dkimPass && !dmarcPass) {\n\t\t\tconst spfFailed = lowerMsAuth.includes('spf=fail');\n\t\t\tconst dkimFailed = lowerMsAuth.includes('dkim=fail');\n\t\t\tconst dmarcFailed = lowerMsAuth.includes('dmarc=fail');\n\n\t\t\tif (spfFailed || dkimFailed || dmarcFailed) {\n\t\t\t\tresult.blocked = true;\n\t\t\t\tresult.reasons.push('MS_EXCHANGE_AUTH_FAILURE');\n\t\t\t\tresult.score += 10;\n\t\t\t\tresult.category = 'AUTHENTICATION_FAILURE';\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Check for vendor-specific spam patterns\n * @param {Object} parsed - Parsed email\n * @param {SessionInfo} sessionInfo - Session information\n * @param {Function} getHeader - Header getter function\n * @param {string} subject - Email subject\n * @param {string} from - From address\n * @returns {{blocked: boolean, reasons: string[], score: number, category: string|null}}\n */\nfunction checkVendorSpam_(parsed, sessionInfo, getHeader, subject, from) {\n\tconst result = {\n\t\tblocked: false, reasons: [], score: 0, category: null,\n\t};\n\tconst fromLower = from.toLowerCase();\n\n\t// PayPal invoice spam\n\tif (\n\t\tsessionInfo.originalFromAddressRootDomain === 'paypal.com'\n\t\t&& getHeader('x-email-type-id')\n\t) {\n\t\tconst typeId = getHeader('x-email-type-id');\n\t\tif (PAYPAL_SPAM_TYPE_IDS.has(typeId)) {\n\t\t\tresult.blocked = true;\n\t\t\tresult.reasons.push(`PAYPAL_INVOICE_SPAM: ${typeId}`);\n\t\t\tresult.score += 15;\n\t\t\tresult.category = 'VENDOR_SPAM';\n\t\t\treturn result;\n\t\t}\n\t}\n\n\t// Authorize.net/VISA phishing scam\n\tif (\n\t\tsessionInfo.originalFromAddress === 'invoice@authorize.net'\n\t\t&& sessionInfo.resolvedRootClientHostname === 'visa.com'\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('AUTHORIZE_VISA_PHISHING');\n\t\tresult.score += 15;\n\t\tresult.category = 'PHISHING';\n\t\treturn result;\n\t}\n\n\t// Amazon.co.jp impersonation\n\tif (\n\t\tfromLower.includes('amazon.co.jp')\n\t\t&& (!sessionInfo.resolvedRootClientHostname\n\t\t\t|| !sessionInfo.resolvedRootClientHostname.startsWith('amazon.'))\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('AMAZON_JP_IMPERSONATION');\n\t\tresult.score += 12;\n\t\tresult.category = 'IMPERSONATION';\n\t\treturn result;\n\t}\n\n\t// PCloud impersonation\n\tif (\n\t\tsubject\n\t\t&& subject.includes('pCloud')\n\t\t&& sessionInfo.originalFromAddressRootDomain !== 'pcloud.com'\n\t\t&& fromLower.includes('pcloud')\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('PCLOUD_IMPERSONATION');\n\t\tresult.score += 12;\n\t\tresult.category = 'IMPERSONATION';\n\t\treturn result;\n\t}\n\n\t// Microsoft postmaster bounce spam\n\tif (\n\t\t(sessionInfo.originalFromAddress === 'postmaster@outlook.com'\n\t\t\t|| (sessionInfo.resolvedClientHostname\n\t\t\t\t&& sessionInfo.resolvedClientHostname.endsWith('.outbound.protection.outlook.com'))\n\t\t\t|| (sessionInfo.originalFromAddress?.startsWith('postmaster@')\n\t\t\t\t&& sessionInfo.originalFromAddress?.endsWith('.onmicrosoft.com')))\n\t\t\t&& isAutoReply(getHeader)\n\t\t\t&& subject\n\t\t\t&& (subject.startsWith('Undeliverable: ') || subject.startsWith('No se puede entregar: '))\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('MS_BOUNCE_SPAM');\n\t\tresult.score += 10;\n\t\tresult.category = 'BOUNCE_SPAM';\n\t\treturn result;\n\t}\n\n\t// 163.com bounce spam\n\tif (\n\t\tsessionInfo.originalFromAddress === 'postmaster@163.com'\n\t\t&& subject\n\t\t&& subject.includes('\u7CFB\u7EDF\u9000\u4FE1')\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('163_BOUNCE_SPAM');\n\t\tresult.score += 10;\n\t\tresult.category = 'BOUNCE_SPAM';\n\t\treturn result;\n\t}\n\n\t// DocuSign + Microsoft scam\n\tif (\n\t\tsessionInfo.originalFromAddress === 'dse_na4@docusign.net'\n\t\t&& sessionInfo.spf?.domain\n\t\t&& (sessionInfo.spf.domain.endsWith('.onmicrosoft.com')\n\t\t\t|| sessionInfo.spf.domain === 'onmicrosoft.com')\n\t) {\n\t\tresult.blocked = true;\n\t\tresult.reasons.push('DOCUSIGN_MS_SCAM');\n\t\tresult.score += 12;\n\t\tresult.category = 'PHISHING';\n\t\treturn result;\n\t}\n\n\treturn result;\n}\n\n/**\n * Check for spoofing attacks\n * @param {Object} parsed - Parsed email\n * @param {SessionInfo} sessionInfo - Session information\n * @param {Function} getHeader - Header getter function\n * @param {string} subject - Email subject\n * @returns {{blocked: boolean, reasons: string[], score: number, category: string|null}}\n */\nfunction checkSpoofingAttacks(parsed, sessionInfo, getHeader, subject) {\n\tconst result = {\n\t\tblocked: false, reasons: [], score: 0, category: null,\n\t};\n\n\t// Skip if DKIM aligned and passing, or if allowlisted\n\tif (sessionInfo.hadAlignedAndPassingDKIM || sessionInfo.isAllowlisted) {\n\t\treturn result;\n\t}\n\n\t// Skip if client hostname matches From domain\n\tif (sessionInfo.hasSameHostnameAsFrom) {\n\t\treturn result;\n\t}\n\n\t// Check if any RCPT TO has same root domain as From header\n\tconst rcptTo = sessionInfo.envelope?.rcptTo || [];\n\tconst fromRootDomain = sessionInfo.originalFromAddressRootDomain;\n\n\tif (!fromRootDomain || rcptTo.length === 0) {\n\t\treturn result;\n\t}\n\n\tconst hasSameRcptToAsFrom = rcptTo.some(to => {\n\t\tif (!to.address) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst toRootDomain = getRootDomain(parseHostFromAddress(to.address));\n\t\treturn toRootDomain === fromRootDomain;\n\t});\n\n\tif (!hasSameRcptToAsFrom) {\n\t\treturn result;\n\t}\n\n\t// Check SPF result\n\tconst spfResult = sessionInfo.spfFromHeader?.status?.result;\n\tif (spfResult === 'pass') {\n\t\treturn result;\n\t}\n\n\t// Mark as potential phishing (for later notification)\n\tsessionInfo.isPotentialPhishing = true;\n\n\t// Allow sysadmin alerts through\n\tconst xPhpScript = getHeader('x-php-script');\n\tconst xMailer = getHeader('x-mailer');\n\n\tif (xPhpScript) {\n\t\treturn result;\n\t}\n\n\tif (xMailer) {\n\t\tconst mailerLower = xMailer.toLowerCase();\n\t\tif (mailerLower.includes('php') || mailerLower.includes('drupal')) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tif (subject && SYSADMIN_SUBJECT_PATTERN.test(subject)) {\n\t\treturn result;\n\t}\n\n\t// This looks like a spoofing attack\n\tresult.blocked = true;\n\tresult.reasons.push('SPOOFING_ATTACK');\n\tresult.score += 12;\n\tresult.category = 'SPOOFING';\n\n\treturn result;\n}\n\n/**\n * Check if message is an auto-reply or from a mailing list\n * @param {Function} getHeader - Header getter function\n * @returns {boolean}\n */\nfunction isAutoReply(getHeader) {\n\t// Check Auto-Submitted header\n\tconst autoSubmitted = getHeader('auto-submitted');\n\tif (autoSubmitted && autoSubmitted !== 'no') {\n\t\treturn true;\n\t}\n\n\t// Check X-Auto-Response-Suppress\n\tconst autoResponseSuppress = getHeader('x-auto-response-suppress');\n\tif (autoResponseSuppress) {\n\t\treturn true;\n\t}\n\n\t// Check Precedence header\n\tconst precedence = getHeader('precedence');\n\tif (precedence && ['bulk', 'junk', 'list', 'auto_reply'].includes(precedence.toLowerCase())) {\n\t\treturn true;\n\t}\n\n\t// Check List-Unsubscribe (mailing list)\n\tif (getHeader('list-unsubscribe')) {\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Check subject line for spam patterns\n * @param {string} subject\n * @returns {{score: number, reasons: string[]}}\n */\nfunction checkSubjectLine(subject) {\n\tconst reasons = [];\n\tlet score = 0;\n\n\t// Check against patterns\n\tfor (const pattern of SPAM_PATTERNS.subjectPatterns) {\n\t\tif (pattern.test(subject)) {\n\t\t\tconst match = subject.match(pattern);\n\t\t\treasons.push(`SUBJECT_SPAM_PATTERN: ${match[0]}`);\n\t\t\tscore += 1;\n\t\t}\n\t}\n\n\t// Check for all caps\n\tconst upperCount = (subject.match(/[A-Z]/g) || []).length;\n\tconst letterCount = (subject.match(/[a-zA-Z]/g) || []).length;\n\tif (letterCount > 10 && upperCount / letterCount > 0.7) {\n\t\treasons.push('SUBJECT_ALL_CAPS');\n\t\tscore += 2;\n\t}\n\n\t// Check for excessive punctuation\n\tconst punctCount = (subject.match(/[!?$]/g) || []).length;\n\tif (punctCount >= 3) {\n\t\treasons.push('SUBJECT_EXCESSIVE_PUNCTUATION');\n\t\tscore += 1;\n\t}\n\n\t// Check for RE:/FW: without proper threading\n\tif (/^(re|fw|fwd):/i.test(subject) && subject.length < 20) {\n\t\treasons.push('SUBJECT_FAKE_REPLY');\n\t\tscore += 1;\n\t}\n\n\treturn {score, reasons};\n}\n\n/**\n * Check body content for spam patterns\n * @param {string} text - Plain text body\n * @param {string} html - HTML body\n * @returns {{score: number, reasons: string[]}}\n */\nfunction checkBodyContent(text, html) {\n\tconst reasons = [];\n\tlet score = 0;\n\n\tconst content = text || html || '';\n\tconst contentLower = content.toLowerCase();\n\n\t// Check against body patterns\n\tfor (const pattern of SPAM_PATTERNS.bodyPatterns) {\n\t\tif (pattern.test(content)) {\n\t\t\tconst match = content.match(pattern);\n\t\t\treasons.push(`BODY_SPAM_PATTERN: ${match[0].slice(0, 50)}`);\n\t\t\tscore += 1;\n\t\t}\n\t}\n\n\t// Check for spam keywords\n\tfor (const [keyword, weight] of SPAM_KEYWORDS) {\n\t\tif (contentLower.includes(keyword.toLowerCase())) {\n\t\t\treasons.push(`SPAM_KEYWORD: ${keyword}`);\n\t\t\tscore += weight;\n\t\t}\n\t}\n\n\t// Check for hidden text (white on white, tiny font, etc.)\n\tif (html) {\n\t\tif (/color:\\s*#fff|color:\\s*white|font-size:\\s*[01]px/i.test(html)) {\n\t\t\treasons.push('HIDDEN_TEXT');\n\t\t\tscore += 3;\n\t\t}\n\n\t\t// Check for excessive images with few text\n\t\tconst imgCount = (html.match(/<img/gi) || []).length;\n\t\tconst textLength = (text || '').length;\n\t\tif (imgCount > 5 && textLength < 100) {\n\t\t\treasons.push('IMAGE_HEAVY_LOW_TEXT');\n\t\t\tscore += 2;\n\t\t}\n\t}\n\n\t// Check for base64 encoded content (often used to evade filters)\n\tif (/data:image\\/[^;]+;base64,/i.test(html || '')) {\n\t\treasons.push('BASE64_IMAGES');\n\t\tscore += 1;\n\t}\n\n\t// Check for URL shorteners\n\tconst shortenerPatterns\n\t\t= /\\b(bit\\.ly|tinyurl|goo\\.gl|t\\.co|ow\\.ly|is\\.gd|buff\\.ly|adf\\.ly|j\\.mp)\\b/i;\n\tif (shortenerPatterns.test(content)) {\n\t\treasons.push('URL_SHORTENER');\n\t\tscore += 2;\n\t}\n\n\treturn {score, reasons};\n}\n\n/**\n * Check sender for suspicious patterns\n * @param {string} from - From address\n * @param {string} replyTo - Reply-To address\n * @returns {{score: number, reasons: string[]}}\n */\nfunction checkSenderPatterns(from, replyTo) {\n\tconst reasons = [];\n\tlet score = 0;\n\n\tif (!from) {\n\t\treasons.push('MISSING_FROM');\n\t\tscore += 2;\n\t\treturn {score, reasons};\n\t}\n\n\t// Check against sender patterns\n\tfor (const pattern of SPAM_PATTERNS.senderPatterns) {\n\t\tif (pattern.test(from)) {\n\t\t\treasons.push('SUSPICIOUS_SENDER_PATTERN');\n\t\t\tscore += 2;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Check for suspicious TLD\n\tconst tldMatch = from.match(/@[^.]+\\.([a-z]+)$/i);\n\tif (tldMatch && SUSPICIOUS_TLDS.has(tldMatch[1].toLowerCase())) {\n\t\treasons.push(`SUSPICIOUS_TLD: ${tldMatch[1]}`);\n\t\tscore += 2;\n\t}\n\n\t// Check for From/Reply-To mismatch\n\tif (replyTo && from) {\n\t\tconst fromDomain = from.split('@')[1]?.toLowerCase();\n\t\tconst replyDomain = replyTo.split('@')[1]?.toLowerCase();\n\t\tif (fromDomain && replyDomain && fromDomain !== replyDomain) {\n\t\t\treasons.push('FROM_REPLY_TO_MISMATCH');\n\t\t\tscore += 2;\n\t\t}\n\t}\n\n\t// Check for display name spoofing\n\t// e.g., \"PayPal <scammer@evil.com>\"\n\tconst spoofPatterns = /^(paypal|amazon|apple|microsoft|google|bank|security)/i;\n\tif (spoofPatterns.test(from) && !/@(paypal|amazon|apple|microsoft|google)\\.com$/i.test(from)) {\n\t\treasons.push('DISPLAY_NAME_SPOOFING');\n\t\tscore += 3;\n\t}\n\n\treturn {score, reasons};\n}\n\n/**\n * Check headers for anomalies\n * @param {Object} parsed - Parsed email\n * @param {Function} getHeader - Header getter function\n * @returns {{score: number, reasons: string[]}}\n */\nfunction checkHeaderAnomalies(parsed, getHeader) {\n\tconst reasons = [];\n\tlet score = 0;\n\n\t// Check for missing Message-ID\n\tif (!parsed.messageId && !getHeader('message-id')) {\n\t\treasons.push('MISSING_MESSAGE_ID');\n\t\tscore += 1;\n\t}\n\n\t// Check for missing Date\n\tif (parsed.date) {\n\t\t// Check for future date\n\t\tconst messageDate = new Date(parsed.date);\n\t\tconst now = new Date();\n\t\tif (messageDate > now) {\n\t\t\tconst hoursDiff = (messageDate - now) / (1000 * 60 * 60);\n\t\t\tif (hoursDiff > 24) {\n\t\t\t\treasons.push('FUTURE_DATE');\n\t\t\t\tscore += 2;\n\t\t\t}\n\t\t}\n\n\t\t// Check for very old date\n\t\tconst daysDiff = (now - messageDate) / (1000 * 60 * 60 * 24);\n\t\tif (daysDiff > 365) {\n\t\t\treasons.push('VERY_OLD_DATE');\n\t\t\tscore += 1;\n\t\t}\n\t} else {\n\t\treasons.push('MISSING_DATE');\n\t\tscore += 1;\n\t}\n\n\t// Check for suspicious X-Mailer\n\tconst xMailer = getHeader('x-mailer') || '';\n\tif (xMailer) {\n\t\tconst suspiciousMailers = /mass mail|bulk mail|email blast/i;\n\t\tif (suspiciousMailers.test(xMailer)) {\n\t\t\treasons.push('SUSPICIOUS_MAILER');\n\t\t\tscore += 1;\n\t\t}\n\t}\n\n\t// Check for missing MIME-Version\n\tconst mimeVersion = getHeader('mime-version');\n\tif (!mimeVersion && (parsed.html || parsed.attachments?.length > 0)) {\n\t\treasons.push('MISSING_MIME_VERSION');\n\t\tscore += 1;\n\t}\n\n\t// Check for excessive recipients\n\tconst toCount = parsed.to?.value?.length || 0;\n\tconst ccCount = parsed.cc?.value?.length || 0;\n\tif (toCount + ccCount > 50) {\n\t\treasons.push('EXCESSIVE_RECIPIENTS');\n\t\tscore += 2;\n\t}\n\n\treturn {score, reasons};\n}\n\n/**\n * Check links in content for suspicious patterns\n * @param {string} content - Email content (HTML or text)\n * @returns {{score: number, reasons: string[]}}\n */\nfunction checkSuspiciousLinks(content) {\n\tconst reasons = [];\n\tlet score = 0;\n\n\t// Extract URLs\n\tconst urlPattern = /https?:\\/\\/[^\\s<>\"']+/gi;\n\tconst urls = content.match(urlPattern) || [];\n\n\tif (urls.length === 0) {\n\t\treturn {score, reasons};\n\t}\n\n\t// Check each URL\n\tconst suspiciousUrls = new Set();\n\tfor (const url of urls) {\n\t\ttry {\n\t\t\tconst parsed = new URL(url);\n\t\t\tconst hostname = parsed.hostname.toLowerCase();\n\n\t\t\t// Check for IP address URLs\n\t\t\tif (/^(?:\\d+\\.){3}\\d+$/.test(hostname)) {\n\t\t\t\tsuspiciousUrls.add('IP_ADDRESS_URL');\n\t\t\t}\n\n\t\t\t// Check for suspicious TLD\n\t\t\tconst tld = hostname.split('.').pop();\n\t\t\tif (SUSPICIOUS_TLDS.has(tld)) {\n\t\t\t\tsuspiciousUrls.add(`SUSPICIOUS_URL_TLD: ${tld}`);\n\t\t\t}\n\n\t\t\t// Check for URL with port\n\t\t\tif (parsed.port && !['80', '443', ''].includes(parsed.port)) {\n\t\t\t\tsuspiciousUrls.add('URL_WITH_PORT');\n\t\t\t}\n\n\t\t\t// Check for very long URLs (often used in phishing)\n\t\t\tif (url.length > 200) {\n\t\t\t\tsuspiciousUrls.add('VERY_LONG_URL');\n\t\t\t}\n\n\t\t\t// Check for excessive subdomains\n\t\t\tconst subdomainCount = hostname.split('.').length - 2;\n\t\t\tif (subdomainCount > 3) {\n\t\t\t\tsuspiciousUrls.add('EXCESSIVE_SUBDOMAINS');\n\t\t\t}\n\n\t\t\t// Check for URL obfuscation (encoded characters)\n\t\t\tif (/%[\\da-f]{2}/i.test(url) && /%[\\da-f]{2}.*%[\\da-f]{2}/i.test(url)) {\n\t\t\t\tsuspiciousUrls.add('URL_OBFUSCATION');\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid URL\n\t\t\tsuspiciousUrls.add('INVALID_URL');\n\t\t}\n\t}\n\n\t// Add unique reasons\n\tfor (const reason of suspiciousUrls) {\n\t\treasons.push(reason);\n\t\tscore += 1;\n\t}\n\n\t// Check for mismatched link text and URL (common in phishing)\n\tconst linkPattern = /<a[^>]+href=[\"']([^\"']+)[\"'][^>]*>([^<]+)<\\/a>/gi;\n\tlet match;\n\twhile ((match = linkPattern.exec(content)) !== null) {\n\t\tconst href = match[1];\n\t\tconst text = match[2];\n\n\t\t// Check if link text looks like a URL but doesn't match href\n\t\tif (/^https?:\\/\\//i.test(text)) {\n\t\t\ttry {\n\t\t\t\tconst textUrl = new URL(text);\n\t\t\t\tconst hrefUrl = new URL(href);\n\t\t\t\tif (textUrl.hostname.toLowerCase() !== hrefUrl.hostname.toLowerCase()) {\n\t\t\t\t\treasons.push('LINK_TEXT_URL_MISMATCH');\n\t\t\t\t\tscore += 3;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore parsing errors\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {score, reasons};\n}\n\n/**\n * Get root domain from a hostname\n * @param {string} hostname\n * @returns {string}\n */\nfunction getRootDomain(hostname) {\n\tif (!hostname) {\n\t\treturn '';\n\t}\n\n\tconst parts = hostname.toLowerCase().split('.');\n\tif (parts.length <= 2) {\n\t\treturn hostname.toLowerCase();\n\t}\n\n\t// Handle common multi-part TLDs\n\tconst multiPartTlds = ['co.uk', 'com.au', 'co.nz', 'co.jp', 'com.br', 'co.in'];\n\tconst lastTwo = parts.slice(-2).join('.');\n\tif (multiPartTlds.includes(lastTwo)) {\n\t\treturn parts.slice(-3).join('.');\n\t}\n\n\treturn parts.slice(-2).join('.');\n}\n\n/**\n * Parse host/domain from an email address\n * @param {string} address - Email address\n * @returns {string}\n */\nfunction parseHostFromAddress(address) {\n\tif (!address) {\n\t\treturn '';\n\t}\n\n\tconst atIndex = address.indexOf('@');\n\tif (atIndex === -1) {\n\t\treturn '';\n\t}\n\n\treturn address.slice(atIndex + 1).toLowerCase();\n}\n\n/**\n * Extract client hostname from email headers\n * @param {Object} parsed - Parsed email\n * @returns {string|null}\n */\nfunction extractClientHostname(parsed) {\n\t// Try to extract from Received headers\n\tlet receivedHeaders = null;\n\tif (parsed.headers?.get) {\n\t\treceivedHeaders = parsed.headers.get('received');\n\t} else if (parsed.headerLines) {\n\t\tconst headers = parsed.headerLines.filter(h => h.key.toLowerCase() === 'received');\n\t\treceivedHeaders = headers.map(h => h.line?.split(':').slice(1).join(':').trim());\n\t}\n\n\tif (!receivedHeaders) {\n\t\treturn null;\n\t}\n\n\tconst received = Array.isArray(receivedHeaders) ? receivedHeaders[0] : receivedHeaders;\n\tif (!received) {\n\t\treturn null;\n\t}\n\n\t// Parse \"from hostname\" pattern\n\tconst fromMatch = received.match(/from\\s+([^\\s(]+)/i);\n\tif (fromMatch) {\n\t\treturn fromMatch[1].toLowerCase();\n\t}\n\n\treturn null;\n}\n\n/**\n * Extract remote IP from email headers\n * @param {Object} parsed - Parsed email\n * @returns {string|null}\n */\nfunction extractRemoteIp(parsed) {\n\t// Try to extract from Received headers\n\tlet receivedHeaders = null;\n\tif (parsed.headers?.get) {\n\t\treceivedHeaders = parsed.headers.get('received');\n\t} else if (parsed.headerLines) {\n\t\tconst headers = parsed.headerLines.filter(h => h.key.toLowerCase() === 'received');\n\t\treceivedHeaders = headers.map(h => h.line?.split(':').slice(1).join(':').trim());\n\t}\n\n\tif (!receivedHeaders) {\n\t\treturn null;\n\t}\n\n\tconst received = Array.isArray(receivedHeaders) ? receivedHeaders[0] : receivedHeaders;\n\tif (!received) {\n\t\treturn null;\n\t}\n\n\t// Parse IP address patterns\n\tconst ipv4Match = received.match(/\\[((?:\\d+\\.){3}\\d+)]/);\n\tif (ipv4Match) {\n\t\treturn ipv4Match[1];\n\t}\n\n\tconst ipv6Match = received.match(/\\[([a-f\\d:]+)]/i);\n\tif (ipv6Match) {\n\t\treturn ipv6Match[1];\n\t}\n\n\treturn null;\n}\n\nexport {\n\tisArbitrary,\n\tcheckSubjectLine,\n\tcheckBodyContent,\n\tcheckMicrosoftExchangeHeaders,\n\tcheckVendorSpam_ as checkVendorSpam,\n\tcheckSpoofingAttacks,\n\tcheckSenderPatterns,\n\tcheckHeaderAnomalies,\n\tcheckSuspiciousLinks,\n\tgetRootDomain,\n\tparseHostFromAddress,\n\textractClientHostname,\n\textractRemoteIp,\n\tbuildSessionInfo,\n\tisAutoReply,\n\tSPAM_PATTERNS,\n\tSPAM_KEYWORDS,\n\tSUSPICIOUS_TLDS,\n\tMS_SPAM_CATEGORIES,\n\tMS_SPAM_VERDICTS,\n\tPAYPAL_SPAM_TYPE_IDS,\n\tBLOCKED_PHRASES_PATTERN,\n\tSYSADMIN_SUBJECT_PATTERN,\n};\n", "/**\n * Get Attributes Module\n * Based on Forward Email's get-attributes helper\n * Extracts email attributes for reputation checking\n *\n * This module extracts various attributes from an email message that can be\n * used for reputation checking against allowlists, denylists, and truth sources.\n *\n * Attributes extracted include:\n * - Client hostname and root hostname\n * - Remote IP address\n * - From header address, domain, and root domain\n * - Reply-To addresses, domains, and root domains\n * - MAIL FROM address, domain, and root domain\n *\n * @see https://github.com/forwardemail/forwardemail.net/blob/master/helpers/get-attributes.js\n */\n\nimport {debuglog} from 'node:util';\n\nconst debug = debuglog('spamscanner:attributes');\n\n/**\n * @typedef {Object} SessionInfo\n * @property {string} [resolvedClientHostname] - Resolved hostname of connecting client\n * @property {string} [resolvedRootClientHostname] - Root domain of resolved client hostname\n * @property {string} [remoteAddress] - IP address of connecting client\n * @property {string} [originalFromAddress] - Email address from From header\n * @property {string} [originalFromAddressDomain] - Domain from From header\n * @property {string} [originalFromAddressRootDomain] - Root domain from From header\n * @property {Object} [envelope] - SMTP envelope\n * @property {Object} [envelope.mailFrom] - MAIL FROM address\n * @property {Array} [envelope.rcptTo] - RCPT TO addresses\n * @property {boolean} [hadAlignedAndPassingDKIM] - Whether DKIM was aligned and passing\n * @property {Object} [spfFromHeader] - SPF result for From header\n * @property {Set} [signingDomains] - Set of DKIM signing domains\n */\n\n/**\n * @typedef {Object} GetAttributesOptions\n * @property {boolean} [isAligned=false] - Only return attributes that are verified and aligned\n * @property {Object} [authResults] - Authentication results from mailauth\n */\n\n/**\n * Check and remove SRS (Sender Rewriting Scheme) encoding from an address\n * @param {string} address - Email address\n * @returns {string} - Address with SRS removed\n */\nfunction checkSRS(address) {\n\tif (!address) {\n\t\treturn '';\n\t}\n\n\t// SRS0 format: SRS0=HHH=TT=domain=local@forwarder.com\n\t// SRS1 format: SRS1=HHH=forwarder.com==HHH=TT=domain=local@forwarder2.com\n\tconst srs0Match = address.match(/^srs0=[^=]+=([^=]+)=([^=]+)=([^@]+)@/i);\n\tif (srs0Match) {\n\t\treturn `${srs0Match[3]}@${srs0Match[2]}`;\n\t}\n\n\tconst srs1Match = address.match(/^srs1=[^=]+=[^=]+==[^=]+=([^=]+)=([^=]+)=([^@]+)@/i);\n\tif (srs1Match) {\n\t\treturn `${srs1Match[3]}@${srs1Match[2]}`;\n\t}\n\n\treturn address;\n}\n\n/**\n * Parse host/domain from an email address or domain string\n * @param {string} addressOrDomain - Email address or domain\n * @returns {string} - Domain portion\n */\nfunction parseHostFromDomainOrAddress(addressOrDomain) {\n\tif (!addressOrDomain) {\n\t\treturn '';\n\t}\n\n\t// If it contains @, extract domain\n\tconst atIndex = addressOrDomain.indexOf('@');\n\tif (atIndex !== -1) {\n\t\treturn addressOrDomain.slice(atIndex + 1).toLowerCase();\n\t}\n\n\t// Otherwise return as-is (already a domain)\n\treturn addressOrDomain.toLowerCase();\n}\n\n/**\n * Get root domain from a hostname\n * @param {string} hostname\n * @returns {string}\n */\nfunction parseRootDomain(hostname) {\n\tif (!hostname) {\n\t\treturn '';\n\t}\n\n\tconst parts = hostname.toLowerCase().split('.');\n\tif (parts.length <= 2) {\n\t\treturn hostname.toLowerCase();\n\t}\n\n\t// Handle common multi-part TLDs\n\tconst multiPartTlds = new Set([\n\t\t'co.uk',\n\t\t'com.au',\n\t\t'co.nz',\n\t\t'co.jp',\n\t\t'com.br',\n\t\t'co.in',\n\t\t'org.uk',\n\t\t'net.au',\n\t\t'com.mx',\n\t\t'com.cn',\n\t\t'com.tw',\n\t\t'com.hk',\n\t\t'co.za',\n\t\t'com.sg',\n\t]);\n\tconst lastTwo = parts.slice(-2).join('.');\n\tif (multiPartTlds.has(lastTwo)) {\n\t\treturn parts.slice(-3).join('.');\n\t}\n\n\treturn parts.slice(-2).join('.');\n}\n\n/**\n * Parse addresses from a header value\n * @param {string|Object|Array} headerValue - Header value (string or parsed object)\n * @returns {string[]} - Array of email addresses\n */\nfunction parseAddresses(headerValue) {\n\tif (!headerValue) {\n\t\treturn [];\n\t}\n\n\t// If it's already an array of address objects\n\tif (Array.isArray(headerValue)) {\n\t\treturn headerValue\n\t\t\t.flatMap(item => {\n\t\t\t\tif (typeof item === 'string') {\n\t\t\t\t\treturn item;\n\t\t\t\t}\n\n\t\t\t\tif (item.address) {\n\t\t\t\t\treturn item.address;\n\t\t\t\t}\n\n\t\t\t\tif (item.value && Array.isArray(item.value)) {\n\t\t\t\t\treturn item.value.map(v => v.address).filter(Boolean);\n\t\t\t\t}\n\n\t\t\t\treturn null;\n\t\t\t})\n\t\t\t.filter(Boolean);\n\t}\n\n\t// If it's an object with value array (mailparser format)\n\tif (headerValue.value && Array.isArray(headerValue.value)) {\n\t\treturn headerValue.value.map(v => v.address).filter(Boolean);\n\t}\n\n\t// If it's a string, try to parse it\n\tif (typeof headerValue === 'string') {\n\t\t// Simple regex to extract email addresses\n\t\tconst emailPattern = /[\\w.+-]+@[\\w.-]+\\.[a-z]{2,}/gi;\n\t\treturn headerValue.match(emailPattern) || [];\n\t}\n\n\treturn [];\n}\n\n/**\n * Get header value from parsed email\n * @param {Object} headers - Headers object\n * @param {string} name - Header name\n * @returns {string|null}\n */\nfunction getHeaders(headers, name) {\n\tif (!headers) {\n\t\treturn null;\n\t}\n\n\t// Mailparser format\n\tif (headers.get) {\n\t\tconst value = headers.get(name);\n\t\tif (value) {\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\tif (value.text) {\n\t\t\t\treturn value.text;\n\t\t\t}\n\n\t\t\tif (value.value && Array.isArray(value.value)) {\n\t\t\t\treturn value.value.map(v => v.address || v.text || v).join(', ');\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// HeaderLines format\n\tif (headers.headerLines) {\n\t\tconst header = headers.headerLines.find(h => h.key.toLowerCase() === name.toLowerCase());\n\t\tif (header) {\n\t\t\treturn header.line?.split(':').slice(1).join(':').trim();\n\t\t}\n\t}\n\n\t// Plain object format\n\tif (typeof headers === 'object') {\n\t\tconst key = Object.keys(headers).find(k => k.toLowerCase() === name.toLowerCase());\n\t\tif (key) {\n\t\t\tconst value = headers[key];\n\t\t\tif (typeof value === 'string') {\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t\tif (Array.isArray(value)) {\n\t\t\t\treturn value[0];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn null;\n}\n\n/**\n * Get attributes from an email for reputation checking\n *\n * @param {Object} parsed - Parsed email message\n * @param {SessionInfo} session - Session information\n * @param {GetAttributesOptions} [options={}] - Options\n * @returns {Promise<string[]>} - Array of unique attributes to check\n */\nasync function getAttributes(parsed, session = {}, options = {}) {\n\tconst {isAligned = false, authResults = null} = options;\n\n\tconst headers = parsed.headers || parsed;\n\n\t// Get Reply-To addresses\n\tconst replyToHeader = getHeaders(headers, 'reply-to');\n\tconst replyToAddresses = parseAddresses(parsed.replyTo || (replyToHeader ? {value: [{address: replyToHeader}]} : null));\n\n\t// Base attributes: client hostname, root hostname, and IP\n\t// NOTE: we don't check HELO command input because it's arbitrary and can be spoofed\n\tconst array = [\n\t\tsession.resolvedClientHostname,\n\t\tsession.resolvedRootClientHostname,\n\t\tsession.remoteAddress,\n\t];\n\n\t// From header attributes\n\tconst from = [\n\t\tsession.originalFromAddress,\n\t\tsession.originalFromAddressDomain,\n\t\tsession.originalFromAddressRootDomain,\n\t];\n\n\t// Reply-To attributes\n\tconst replyTo = [];\n\tfor (const addr of replyToAddresses) {\n\t\tconst checked = checkSRS(addr);\n\t\treplyTo.push(\n\t\t\tchecked.toLowerCase(),\n\t\t\tparseHostFromDomainOrAddress(checked),\n\t\t\tparseRootDomain(parseHostFromDomainOrAddress(checked)),\n\t\t);\n\t}\n\n\t// MAIL FROM attributes\n\tconst mailFrom = [];\n\tconst mailFromAddress = session.envelope?.mailFrom?.address;\n\tif (mailFromAddress) {\n\t\tconst checked = checkSRS(mailFromAddress);\n\t\tmailFrom.push(\n\t\t\tchecked.toLowerCase(),\n\t\t\tparseHostFromDomainOrAddress(checked),\n\t\t\tparseRootDomain(parseHostFromDomainOrAddress(checked)),\n\t\t);\n\t}\n\n\tif (isAligned) {\n\t\t// Only include attributes that are verified and aligned\n\t\tconst signingDomains = session.signingDomains || new Set();\n\t\tconst spfResult = session.spfFromHeader?.status?.result;\n\n\t\t// Check if From header has SPF pass or DKIM alignment\n\t\tconst fromHasSpfPass = spfResult === 'pass';\n\t\tconst fromHasDkimAlignment\n\t\t\t= signingDomains.size > 0\n\t\t\t\t&& (signingDomains.has(session.originalFromAddressDomain)\n\t\t\t\t\t|| signingDomains.has(session.originalFromAddressRootDomain));\n\n\t\tif (fromHasSpfPass || fromHasDkimAlignment) {\n\t\t\tarray.push(...from);\n\t\t}\n\n\t\t// Check Reply-To alignment\n\t\tlet hasAlignedReplyTo = false;\n\t\tfor (const addr of replyToAddresses) {\n\t\t\tconst checked = checkSRS(addr);\n\t\t\tconst domain = parseHostFromDomainOrAddress(checked);\n\t\t\tconst rootDomain = parseRootDomain(domain);\n\n\t\t\t// Check DKIM alignment\n\t\t\tif (signingDomains.size > 0 && (signingDomains.has(domain) || signingDomains.has(rootDomain))) {\n\t\t\t\thasAlignedReplyTo = true;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Check SPF for Reply-To (if we have auth results)\n\t\t\tif (authResults?.spf) {\n\t\t\t\tconst spfForReplyTo = authResults.spf.find(r => r.domain === domain || r.domain === rootDomain);\n\t\t\t\tif (spfForReplyTo?.result === 'pass') {\n\t\t\t\t\thasAlignedReplyTo = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (hasAlignedReplyTo) {\n\t\t\tarray.push(...replyTo);\n\t\t}\n\n\t\t// Check MAIL FROM alignment\n\t\tif (mailFromAddress) {\n\t\t\tconst checked = checkSRS(mailFromAddress);\n\t\t\tconst domain = parseHostFromDomainOrAddress(checked);\n\t\t\tconst rootDomain = parseRootDomain(domain);\n\n\t\t\tconst mailFromHasDkimAlignment\n\t\t\t\t= signingDomains.size > 0 && (signingDomains.has(domain) || signingDomains.has(rootDomain));\n\n\t\t\t// Check SPF for MAIL FROM\n\t\t\tlet mailFromHasSpfPass = false;\n\t\t\tif (authResults?.spf) {\n\t\t\t\tconst spfForMailFrom = authResults.spf.find(r => r.domain === domain || r.domain === rootDomain);\n\t\t\t\tmailFromHasSpfPass = spfForMailFrom?.result === 'pass';\n\t\t\t}\n\n\t\t\tif (mailFromHasDkimAlignment || mailFromHasSpfPass) {\n\t\t\t\tarray.push(...mailFrom);\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Include all attributes without alignment check\n\t\tarray.push(...from, ...replyTo, ...mailFrom);\n\t}\n\n\t// Normalize and deduplicate\n\tconst normalized = array\n\t\t.filter(string_ => typeof string_ === 'string' && string_.length > 0)\n\t\t.map(string_ => {\n\t\t\ttry {\n\t\t\t\t// Convert to ASCII (punycode) and lowercase\n\t\t\t\treturn string_.toLowerCase().trim();\n\t\t\t} catch {\n\t\t\t\treturn string_.toLowerCase().trim();\n\t\t\t}\n\t\t});\n\n\tconst unique = [...new Set(normalized)];\n\n\tdebug('Extracted %d unique attributes (isAligned=%s): %o', unique.length, isAligned, unique);\n\n\treturn unique;\n}\n\n/**\n * Build session info from parsed email\n * @param {Object} parsed - Parsed email\n * @param {Object} [existingSession={}] - Existing session info to merge\n * @returns {SessionInfo}\n */\nfunction buildSessionFromParsed(parsed, existingSession = {}) {\n\tconst session = {...existingSession};\n\tconst headers = parsed.headers || parsed;\n\n\t// Extract From address info\n\tconst fromHeader = getHeaders(headers, 'from');\n\tconst fromAddresses = parseAddresses(parsed.from || fromHeader);\n\tconst fromAddress = fromAddresses[0];\n\n\tif (fromAddress && !session.originalFromAddress) {\n\t\tsession.originalFromAddress = checkSRS(fromAddress).toLowerCase();\n\t\tsession.originalFromAddressDomain = parseHostFromDomainOrAddress(session.originalFromAddress);\n\t\tsession.originalFromAddressRootDomain = parseRootDomain(session.originalFromAddressDomain);\n\t}\n\n\t// Extract client hostname from Received headers\n\tif (!session.resolvedClientHostname) {\n\t\tconst receivedHeader = getHeaders(headers, 'received');\n\t\tif (receivedHeader) {\n\t\t\tconst received = Array.isArray(receivedHeader) ? receivedHeader[0] : receivedHeader;\n\t\t\tconst fromMatch = received?.match(/from\\s+([^\\s(]+)/i);\n\t\t\tif (fromMatch) {\n\t\t\t\tsession.resolvedClientHostname = fromMatch[1].toLowerCase();\n\t\t\t\tsession.resolvedRootClientHostname = parseRootDomain(session.resolvedClientHostname);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Extract remote IP from Received headers\n\tif (!session.remoteAddress) {\n\t\tconst receivedHeader = getHeaders(headers, 'received');\n\t\tif (receivedHeader) {\n\t\t\tconst received = Array.isArray(receivedHeader) ? receivedHeader[0] : receivedHeader;\n\t\t\tconst ipv4Match = received?.match(/\\[((?:\\d+\\.){3}\\d+)]/);\n\t\t\tif (ipv4Match) {\n\t\t\t\tsession.remoteAddress = ipv4Match[1];\n\t\t\t} else {\n\t\t\t\tconst ipv6Match = received?.match(/\\[([a-f\\d:]+)]/i);\n\t\t\t\tif (ipv6Match) {\n\t\t\t\t\tsession.remoteAddress = ipv6Match[1];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Build envelope from headers if not provided\n\tif (!session.envelope) {\n\t\tsession.envelope = {\n\t\t\tmailFrom: {address: session.originalFromAddress || ''},\n\t\t\trcptTo: [],\n\t\t};\n\n\t\t// Get RCPT TO from To and Cc headers\n\t\tconst toAddresses = parseAddresses(parsed.to || getHeaders(headers, 'to'));\n\t\tconst ccAddresses = parseAddresses(parsed.cc || getHeaders(headers, 'cc'));\n\n\t\tfor (const addr of [...toAddresses, ...ccAddresses]) {\n\t\t\tif (addr) {\n\t\t\t\tsession.envelope.rcptTo.push({address: addr});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn session;\n}\n\n/**\n * Extract all checkable attributes from an email\n * This is a convenience function that combines building session and getting attributes\n *\n * @param {Object} parsed - Parsed email message\n * @param {Object} [options={}] - Options\n * @param {boolean} [options.isAligned=false] - Only return aligned attributes\n * @param {string} [options.senderIp] - Sender IP address\n * @param {string} [options.senderHostname] - Sender hostname\n * @param {Object} [options.authResults] - Authentication results\n * @returns {Promise<{attributes: string[], session: SessionInfo}>}\n */\nasync function extractAttributes(parsed, options = {}) {\n\tconst {isAligned = false, senderIp, senderHostname, authResults} = options;\n\n\t// Build session from parsed email and options\n\tconst session = buildSessionFromParsed(parsed, {\n\t\tremoteAddress: senderIp,\n\t\tresolvedClientHostname: senderHostname,\n\t\tresolvedRootClientHostname: senderHostname ? parseRootDomain(senderHostname) : undefined,\n\t});\n\n\t// Add DKIM signing domains from auth results\n\tif (authResults?.dkim) {\n\t\tsession.signingDomains = new Set();\n\t\tfor (const dkimResult of authResults.dkim) {\n\t\t\tif (dkimResult.result === 'pass' && dkimResult.domain) {\n\t\t\t\tsession.signingDomains.add(dkimResult.domain);\n\t\t\t\tsession.signingDomains.add(parseRootDomain(dkimResult.domain));\n\t\t\t}\n\t\t}\n\n\t\t// Check if DKIM was aligned with From\n\t\tsession.hadAlignedAndPassingDKIM\n\t\t\t= session.signingDomains.has(session.originalFromAddressDomain)\n\t\t\t\t|| session.signingDomains.has(session.originalFromAddressRootDomain);\n\t}\n\n\t// Add SPF result from auth results\n\tif (authResults?.spf) {\n\t\tconst spfForFrom = authResults.spf.find(r =>\n\t\t\tr.domain === session.originalFromAddressDomain\n\t\t\t|| r.domain === session.originalFromAddressRootDomain);\n\t\tif (spfForFrom) {\n\t\t\tsession.spfFromHeader = {\n\t\t\t\tstatus: {result: spfForFrom.result},\n\t\t\t};\n\t\t}\n\t}\n\n\t// Get attributes\n\tconst attributes = await getAttributes(parsed, session, {isAligned, authResults});\n\n\treturn {attributes, session};\n}\n\nexport {\n\tgetAttributes,\n\tbuildSessionFromParsed,\n\textractAttributes,\n\tcheckSRS,\n\tparseHostFromDomainOrAddress,\n\tparseRootDomain,\n\tparseAddresses,\n\tgetHeaders,\n};\n"],
5
+ "mappings": ";;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA;AAAA;;;ACbA;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAAA,iBAAe;AACvB,SAAQ,oBAAmB;AAC3B,OAAO,wBAAwB;AAF/B,IAKMC,QAEA,eAMF,cAUG;AAvBP;AAAA;AAGA;AAEA,IAAMA,SAAQD,UAAS,aAAa;AAEpC,IAAM,gBAAgB;AAAA,MACrB,QAAQ;AAAA,MACR,YAAY;AAAA,IACb;AAGA,IAAI,eAAe,CAAC;AACpB,QAAI;AACH,qBAAe,KAAK,MAAM,aAAa,uBAAuB,MAAM,CAAC;AAAA,IACtE,SAAS,OAAO;AACf,MAAAC,OAAM,KAAK;AACX,iBAAW,eAAe,2BAAmB;AAC5C,qBAAa,WAAW,IAAI,GAAG,WAAW,GAAG,mBAAmB,aAAa,CAAC;AAAA,MAC/E;AAAA,IACD;AAEA,IAAO,uBAAQ;AAAA;AAAA;;;ACvBf;AAAA;AAAA;AAAA;AAAA,SAAQ,YAAAC,iBAAe;AACvB,SAAQ,gBAAAC,qBAAmB;AAC3B,OAAO,gBAAgB;AAFvB,IAIMC,QAEF,YAQG;AAdP;AAAA;AAIA,IAAMA,SAAQF,UAAS,aAAa;AAEpC,IAAI,aAAa,IAAI,WAAW,EAAE,aAAa;AAE/C,QAAI;AACH,mBAAa,KAAK,MAAMC,cAAa,qBAAqB,MAAM,CAAC;AAAA,IAClE,SAAS,OAAO;AACf,MAAAC,OAAM,KAAK;AAAA,IACZ;AAEA,IAAO,yBAAQ;AAAA;AAAA;;;ACdf;AAAA;AAAA;AAAA;AAMA,SAAQ,kBAAiB;AACzB,OAAO,iBAAiB;AAPxB,IAUM,kBAsEA,wBAWA,gBA6BA,qBAgZC;AAxgBP;AAAA;AAUA,IAAM,mBAAmB,oBAAI,IAAI;AAAA;AAAA,MAEhC,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA;AAAA,MAGT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA;AAAA,MAGT,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA,MACV,CAAC,aAAM,GAAG;AAAA;AAAA,MAGV,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MACT,CAAC,UAAK,GAAG;AAAA,MA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,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA,MACL,KAAK;AAAA;AAAA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",
6
+ "names": ["debuglog", "debug", "debuglog", "readFileSync", "debug", "Buffer", "readFileSync", "path", "process", "fileURLToPath", "Buffer", "createHash", "debuglog", "NaiveBayes", "Buffer", "debuglog", "debug", "debuglog", "debug", "debuglog", "debug", "replacements", "classifier", "debug", "debuglog", "NaiveBayes", "path", "createHash", "Buffer", "EnhancedIDNDetector", "detected", "__filename", "fileURLToPath", "__dirname", "path", "readFileSync", "process", "cache", "chunks", "Buffer", "debug"]
7
+ }