Email Header Analyzer

A command line tool that reads one .eml file, applies explainable heuristic rules to its headers, authentication results, domain alignment, URLs and attachments, and produces a risk indication as a single JSON verdict.

shell
python analyze.py samples/suspicious.eml
Dependencies
Zero
Runtime
Python standard library only
Rules
46, three weight values
Output
One JSON verdict

Source on GitHub

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.

Score distribution across the 0 to 6 scale Seven calibration samples plotted on a scale from 0 to 6. The LOW band covers scores 0 to 2, MEDIUM covers 3 to 4, HIGH covers 5 to 6. Four legitimate samples scored 0 and one suspicious sample scored 0, so five samples sit at 0. One suspicious sample scored 1. One suspicious sample scored 3, the only sample to reach MEDIUM. No sample reached the HIGH band. LOW 0-2 MEDIUM 3-4 HIGH 5-6 0 1 2 3 4 5 6 5 samples 1 1 HIGH: never produced by any message Legitimate (open circle) Suspicious (filled circle)
Where the seven calibration samples landed on the 0 to 6 scale. Five samples scored 0, one scored 1, one scored 3. The HIGH band has never been produced by any message, real or synthetic.
Measured outcome per sample group, seven messages
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 .eml file 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.

json
{
  "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.