What it actually caught
The rules were calibrated against seven real messages from my own mailbox: four legitimate, three suspicious. These are the measured results, published as measured.
| Group | Messages | Score | Band | Reading |
|---|---|---|---|---|
| Legitimate | 4 | 0 | LOW | No false positives |
| Suspicious | 1 | 3 | MEDIUM | Detected |
| Suspicious | 1 | 1 | LOW | False negative, structural cause recorded |
| Suspicious | 1 | 0 | LOW | False negative, structural cause recorded |
Two of the three suspicious samples remained in LOW. Both are documented false negatives with recorded structural causes. The weights were deliberately not adjusted to make the suspicious samples score higher. Tuning a scale until the known-bad inputs pass is how a measurement stops meaning anything.
What it does
- Takes one
.emlfile and outputs one JSON verdict. Python standard library only, zero dependencies. - Parses headers: From, Reply-To, Return-Path, To, Subject, Date, and the full Received chain.
- Reads authentication verdicts reported by the receiving mail system from the Authentication-Results header. It performs no DNS lookup, does not validate SPF, does not check a DKIM signature cryptographically, and does not evaluate DMARC policy. It reports what the receiving mail server already stated.
- Reports domain alignment facts: the From domain against the Return-Path domain and
against the DKIM
d=signing domain. - Extracts URLs from plain-text and HTML bodies, comparing the hostname in the visible anchor text against the hostname the link actually points to.
- Records attachment metadata: filename, extension, size and SHA-256. It never opens attachment content and performs no magic-byte identification.
- Applies weighted rule-based scoring and produces a risk indication, not a definitive malware verdict.
How the scoring works
46 rules, each carrying one of three weight values: 3, 1 or 0. A rule contributes its weight once when it fires, whatever its count. The maximum attainable score is 6.
Grouping
Correlated rules are grouped, and a group contributes the maximum of its firing members, never the sum. Four authentication rules at weight 3 each still contribute only 3 in total. Four symptoms of one authentication failure are one piece of evidence, not four.
Bands
- LOW score 0 to 2
- MEDIUM score 3 to 4
- HIGH score 5 to 6
Weight 0 rules stay in the registry rather than being deleted. A rule absent from the table is indistinguishable from a rule nobody thought of; a rule at zero records that it was considered, measured and excluded.
Sample JSON output
A full verdict runs to several hundred lines, because it carries one entry for every one of the 46 rules. The block below is abridged: the per-rule results array and the URL and attachment sections are omitted, and reserved documentation domains stand in place of message data. Every key shown is a real key from the output.
{
"schema_version": 1,
"source_name": "suspicious.eml",
"headers": {
"headers": {
"From": ["Billing <billing@example.com>"],
"Return-Path": [],
"To": ["recipient@example.net"]
},
"received": { "chain": [], "count": 0, "order": "newest_first" }
},
"authentication": [],
"alignment": {
"from_domains": ["example.com"],
"return_path": { "state": "absent", "domains": [] },
"comparisons": []
},
"scoring": {
"total": 3,
"band": "MEDIUM",
"group_contributions": { "A": 3 },
"contributing_rules": [
{ "rule_id": "dmarc_not_evaluated", "weight": 3, "group": "A" }
],
"maximum_attainable": 6
}
}
Limitations
These are the limits of the tool as measured, not a disclaimer. They carry the same weight as the feature list above.
- Seven samples is enough for demonstration and initial calibration. It is not enough for statistically reliable production tuning.
- Zero false positives is an absence of evidence, not evidence of resistance. All four legitimate samples scored exactly zero, so none came near a band boundary.
- No DNS, no cryptographic verification, no external threat intelligence, no sandboxing.
- Alignment checking approximates DMARC relaxed alignment. The Public Suffix List is out of scope, so the organisational domain is guessed structurally.
- The Received chain is the claimed delivery path. Nothing in the tool establishes which relay is trustworthy.
- No magic-byte file identification, which is the largest gap in attachment analysis.
- No automated test framework. Verification was by comparison runs over a fixture set.
AI-assisted development disclosure
The code was written with AI assistance, using Claude Code. The design decisions, the scoring rationale, the calibration against real messages and the verification were mine. The measurements were run on my own machine; the assistant never read the real samples.
Why I built it
A portfolio project by an M.Sc. Cyber Security student at BTU Cottbus-Senftenberg, RHCSA and RHCE certified, targeting SOC Analyst and System or Cloud Administration roles in Germany. Header and authentication analysis is core SOC work, which is why it was the first project rather than a later one.