Phishing detection engine for raw MIME email.
Detects modern phishing techniques that traditional spam filters miss: zero-width Unicode steganography, Cyrillic homoglyph spoofing, display name impersonation, header consistency issues (SPF/DKIM/DMARC), newly registered domains, HREF/display-text URL mismatches, and QR codes in image attachments (quishing).
Phishers embed invisible Unicode characters (zero-width joiners, tag characters, soft hyphens) between letters of brand names to defeat keyword matching:
Display name: D\u2060\u2062i\U000e0020s\U000e0041n\u2061ey+
Renders as: Disney+
Regex extracts: (nothing)
They also use Cyrillic letters that look identical to Latin ones (А→A, о→o), so "disney" written with Cyrillic homoglyphs never matches ASCII brand keywords.
mimeguard normalizes all of this before pattern matching, then layers on
header analysis, domain reputation, and link inspection.
pip install mimeguard
# Optional: QR code scanning
pip install mimeguard[qr]
# Also requires libzbar0: sudo apt install libzbar0 (Linux)from mimeguard import scan, Policy
# Scan raw email bytes
result = scan(raw_email_bytes)
if result.is_phish:
print(f"Action: {result.action.value}")
print(f"Signals: {result.signals}")With a custom policy:
from mimeguard import Policy
policy = Policy(
brand_keywords=["paypal", "disney", "mybank"],
allowlist_domains=["mybank.com"],
filing_domains=["newsletter.example.com"],
)
result = scan(raw_email_bytes, policy)With SpamAssassin score + attachments:
result = scan(
raw_email_bytes,
sa_score=4.2,
attachments=[{"contentType": "image/png", "contentBytes": base64_data}],
)# Scan a file
mimeguard scan suspicious.eml
# JSON output
mimeguard scan suspicious.eml --json
# Stdin
cat email.eml | mimeguard scan -
# Multiple files
mimeguard scan *.eml --quietExit codes: 0=clean, 1=error, 2=phish detected.
| Signal | Trigger | Auto-Junk? |
|---|---|---|
zero_width_obfuscation |
Invisible Unicode in display name or subject | Yes |
brand_spoof:<brand> |
Brand keyword in subject/body from non-allowlisted sender | If freemail |
displayname_localpart_mismatch |
Display name shares no overlap with email local-part | If SA≥1.0 |
reply_to_mismatch:<domain> |
Reply-To goes to different domain than From | If +other signal |
return_path_mismatch:<domain> |
Return-Path differs from From | Report level |
spf_fail / dkim_fail / dmarc_fail |
Authentication failure in MIME headers | If +other signal |
newly_registered_domain:<N>d |
Sender domain registered <30 days ago (RDAP) | If <14 days |
href_mismatch:<brand>-><domain> |
Link text shows brand but href goes elsewhere | Yes |
qr_url:<url> |
QR code in image attachment contains a URL | Yes |
urgency+link |
Urgency language + clickable URL | Report level |
credential_language |
Password/payment harvest phrases | Report level |
lookalike_domain |
Domain uses common lookalike patterns | Report level |
mimeguard/
├── normalize.py Unicode pipeline: NFKC + strip invisibles + homoglyphs
├── headers.py MIME header consistency: From/Reply-To/Return-Path/SPF/DKIM/DMARC
├── domain.py RDAP domain age lookup with caching (NRDD)
├── href.py HREF vs display-text URL mismatch detection
├── qr.py QR code scanning from image attachments (pyzbar/zbarimg)
├── policy.py Configuration: brands, allowlists, thresholds
├── models.py PhishResult dataclass + Action enum
├── engine.py Core scan() function — ties it all together
└── cli.py Command-line interface
- SANS ISC: "Shy Z-WASP" zero-width character phishing (2024)
- Microsoft Security: "How modern phishing emails hide in plain sight" (2021)
- Unicode Technical Standard #39: Unicode Security Mechanisms (confusables)
- APWG: 400% increase in QR code phishing (2024-2025)
- DNSRF: 63% of phishing domains are <4 days old
- Intezer: URL Analysis 101 for phishing detection
MIT