#!/usr/bin/env python3 """BEC detection agent - analyzes email headers and content for Business Email Compromise indicators. Parses email headers for spoofing signals, checks DMARC/SPF/DKIM alignment, detects urgency language patterns, and flags financial request anomalies. """ import argparse import email import json import re import sys from email import policy from pathlib import Path BEC_URGENCY_PATTERNS = [ r"\b(urgent|immediately|asap|right away|time.?sensitive)\b", r"\b(confidential|do not share|keep this between us|don't tell)\b", r"\b(wire transfer|bank transfer|payment|invoice|routing number)\b", r"\b(gift card|bitcoin|crypto|western union|moneygram)\b", r"\b(ceo|cfo|president|director) (asked|requested|needs|wants)\b", r"\b(change.*(bank|account|payment)|new.*(bank|account|routing))\b", r"\b(act now|deadline today|end of day|before close)\b", ] EXECUTIVE_TITLES = ["ceo", "cfo", "coo", "cto", "president", "chairman", "managing director", "vice president", "vp", "director"] def parse_email_file(filepath): with open(filepath, "r", encoding="utf-8", errors="replace") as f: return email.message_from_file(f, policy=policy.default) def check_spf_dkim_dmarc(msg): results = {"spf": "none", "dkim": "none", "dmarc": "none"} auth_results = msg.get("Authentication-Results", "") if "spf=pass" in auth_results.lower(): results["spf"] = "pass" elif "spf=fail" in auth_results.lower(): results["spf"] = "fail" if "dkim=pass" in auth_results.lower(): results["dkim"] = "pass" elif "dkim=fail" in auth_results.lower(): results["dkim"] = "fail" if "dmarc=pass" in auth_results.lower(): results["dmarc"] = "pass" elif "dmarc=fail" in auth_results.lower(): results["dmarc"] = "fail" return results def check_display_name_spoofing(msg, vip_names): from_header = msg.get("From", "") match = re.match(r'"?([^"<]+)"?\s*<([^>]+)>', from_header) if not match: return None display_name = match.group(1).strip().lower() email_addr = match.group(2).strip().lower() for vip in vip_names: if vip.lower() in display_name: domain = email_addr.split("@")[-1] if "@" in email_addr else "" return {"display_name": display_name, "email": email_addr, "matched_vip": vip, "domain": domain, "indicator": "Display name matches VIP but email may be external"} return None def check_reply_to_mismatch(msg): from_addr = msg.get("From", "") reply_to = msg.get("Reply-To", "") if not reply_to: return None from_match = re.search(r'<([^>]+)>', from_addr) or re.search(r'(\S+@\S+)', from_addr) reply_match = re.search(r'<([^>]+)>', reply_to) or re.search(r'(\S+@\S+)', reply_to) if from_match and reply_match: from_email = from_match.group(1).lower() reply_email = reply_match.group(1).lower() from_domain = from_email.split("@")[-1] reply_domain = reply_email.split("@")[-1] if from_domain != reply_domain: return {"from": from_email, "reply_to": reply_email, "indicator": "Reply-To domain differs from From domain"} return None def detect_urgency_language(body): matches = [] for pattern in BEC_URGENCY_PATTERNS: found = re.findall(pattern, body, re.IGNORECASE) if found: matches.extend(found) return matches def calculate_bec_score(auth, spoofing, reply_mismatch, urgency_matches): score = 0 if auth.get("spf") == "fail": score += 25 if auth.get("dkim") == "fail": score += 20 if auth.get("dmarc") == "fail": score += 30 if spoofing: score += 35 if reply_mismatch: score += 25 score += min(len(urgency_matches) * 10, 40) return min(score, 100) def analyze_email(filepath, vip_names): msg = parse_email_file(filepath) body = msg.get_body(preferencelist=("plain", "html")) body_text = body.get_content() if body else "" auth = check_spf_dkim_dmarc(msg) spoofing = check_display_name_spoofing(msg, vip_names) reply_mismatch = check_reply_to_mismatch(msg) urgency = detect_urgency_language(body_text) score = calculate_bec_score(auth, spoofing, reply_mismatch, urgency) risk = "CRITICAL" if score >= 70 else "HIGH" if score >= 50 else "MEDIUM" if score >= 30 else "LOW" return { "file": str(filepath), "from": msg.get("From", ""), "to": msg.get("To", ""), "subject": msg.get("Subject", ""), "date": msg.get("Date", ""), "authentication": auth, "display_name_spoofing": spoofing, "reply_to_mismatch": reply_mismatch, "urgency_indicators": urgency, "bec_score": score, "risk_level": risk, } def main(): parser = argparse.ArgumentParser(description="BEC Email Analyzer") parser.add_argument("--email-file", required=True, help="Path to .eml file") parser.add_argument("--vip-names", nargs="+", default=[], help="VIP display names to check") parser.add_argument("--scan-dir", help="Scan all .eml files in directory") args = parser.parse_args() results = [] if args.scan_dir: for eml in Path(args.scan_dir).glob("*.eml"): results.append(analyze_email(str(eml), args.vip_names)) else: results.append(analyze_email(args.email_file, args.vip_names)) print(json.dumps(results, indent=2)) if __name__ == "__main__": main()