← All posts
Phishing

Email authentication proves a domain, not a sender

SPF, DKIM and DMARC can all pass on a message designed to rob you, and routinely do. Here is what each check actually asks, which one looks at the address you can see, and why three green ticks are so often a statement about a domain you were not looking at.

Open the headers on a phishing email that got through and you will usually find something deflating: spf=pass, dkim=pass, dmarc=pass. Every check green.

This is not a failure of the checks. They did exactly what they were built to do, and the answer they gave was true. The trouble is that almost nobody — including a lot of people who configure these records for a living — can say precisely what question each one answers, and all three answer a narrower question than the summary line suggests.

SPF checks an address you never see

SPF asks one thing: did this message arrive from an IP address that the domain in the envelope sender has authorised?

The envelope sender is not the From line. It is the MAIL FROM of the SMTP conversation, surfaced in the headers as Return-Path, and it is the address bounces go to. In almost every piece of bulk and transactional mail it is a different domain from the one you see — a bounce domain belonging to the sending platform.

So a message with From: "DocuSign" <[email protected]> and Return-Path: <[email protected]> gets an SPF check against mail-7734.sendgrid.net. That record is correct, the IP is listed, and SPF passes. SPF has formed no opinion whatsoever about DocuSign, because it was never asked about DocuSign.

SPF also breaks on perfectly legitimate mail. When a mailing list or a forwarding rule re-sends your message, it goes out from the forwarder's servers, which are not in your SPF record. The result is spf=fail on a genuine message, every day, at enormous scale. A failing SPF is not a reliable sign of forgery, and a passing one is not a statement about the brand in the From line.

DKIM checks that the message was not altered

DKIM is a signature. The sending server signs a selected set of headers and the body with a private key, and publishes the public key in DNS under a domain it names in the signature as d=. The receiver fetches that key and verifies.

Two things follow that matter in practice.

First, because the signature travels inside the message, it survives forwarding. This is why the mailing-list case shows spf=fail and dkim=pass together, which looks alarming and is normal.

Second, dkim=pass means this message was signed by whoever controls the d= domain, and has not been modified since. Again — the d= domain. Not the From line. A DKIM signature from mail-7734.sendgrid.net is a true statement about that domain and an empty one about the brand being impersonated.

The failure mode worth learning here is specific. When you see dkim=fail (body hash did not verify), the body is not the body that was signed. Someone changed the message after it left the signer. On a transactional notice asking for money, the modification is the ask.

DMARC is the only one that looks at the From line

DMARC exists because the first two leave a gap you can drive a campaign through. It adds one requirement: at least one of SPF or DKIM must pass and align with the domain in the From header — the address a human actually sees.

Alignment comes in two strengths. Relaxed, which is the common default, accepts a different subdomain of the same registrable domain, so a From of acme.atlassian.net aligns with a DKIM d=atlassian.net. Strict demands an exact match.

That is the whole mechanism, and it is genuinely good. It is also why the DocuSign example above carries dmarc=none rather than dmarc=fail: nobody ever asked the question, because the domain in the From line publishes no DMARC record at all.

The policy is the part that does the work

A DMARC record carries a policy: p=none, p=quarantine or p=reject.

p=none means tell me about failures and deliver the mail anyway. It is the correct place to start, because it lets you find your own forgotten senders before you break them. It is also where an enormous number of domains stop permanently, because the reports arrive, the work to fix the stragglers never gets prioritised, and a year later the domain is still publishing a record that blocks nothing.

A domain on p=none is not protected by DMARC. It is instrumented by DMARC. Those are different things, and only one of them stops a forgery reaching an inbox. If you own a domain, the single most useful thing in this article is to go and check which one yours publishes.

Three forgeries that pass every check

Once you accept that these protocols authenticate a domain, the standard attacks stop being surprising.

The lookalike domain. rnicrosoft-support.com. An r beside an n reads as an m in most proportional fonts, and your eye completes the word before you have finished looking at it. The attacker genuinely owns that domain, so they publish a real SPF record, sign with real DKIM, and set p=reject if they feel like it. Everything passes, because everything is true.

The subdomain. amazon.in.order-update.co. Read it right to left: the registrable domain is order-update.co, and everything to the left of it is a label the owner invented. They can invent any string, including one that is a brand you trust. This survives every authentication check for the same reason — they own the domain being authenticated.

The display name. From: "CEO Komal Mishra" <[email protected]>. There is no forgery here at all. Gmail authenticates perfectly, because Google runs a competent mail platform for everyone, including whoever is sending this. The display name is free text and no protocol checks it against anything.

In all three cases the authentication result is correct and useless, because it describes a domain you were not looking at.

The case authentication cannot help with

There is one more, and it is the worst.

From: [email protected], sent from acme.com's own servers, aligned SPF, aligned DKIM, dmarc=pass, asking you to update bank details before Friday. Everything is green and the message is real. Either payroll sent it, or somebody is sitting inside an acme.com mailbox.

Authentication answers who sent this. It has never answered should I do what it says. A compromised account produces flawless authentication, which is precisely why business email compromise is the expensive one — it skips the whole category of defence this article is about.

What to actually look at

In rough order of how much it tells you:

The registrable domain in the From line, read right to left — the last two labels before the first slash or the end. Everything else in the hostname was chosen by whoever owns that.

The d= on the DKIM result. If it is not the brand's own domain, the signature is telling you about a sending platform, not the brand.

The DMARC policy in brackets. p=reject means a forgery from that domain would have been thrown away before it reached you. p=none means the check ran and nothing would have happened either way.

A diverging Reply-To. Not forgery, and worth escalating anyway: a reply address pointing somewhere unrelated to the sender is either a sloppy process or a thread someone is preparing to hijack.

X-Mailer, when it is wrong. Corporate mail announces itself as a mail client. A command-line SMTP tool announcing itself on an internal message means someone with a script is on a machine your relay trusts.

You can paste a raw message into the phishing email analyzer and have it lay all of this out — what each check concluded, what aligned with what, and what none of it proves. It runs in your browser, which matters here, because a real phishing email in your inbox is evidence and you should not be uploading it to a stranger's web form.

And if you want the reading to become automatic rather than deliberate, the daily drill puts a set of raw headers in front of you every fifth morning. Two minutes. The point is not the score — it is that dkim=pass header.d=mail-7734.sendgrid.net eventually stops looking reassuring and starts looking like what it is: a sentence about somebody else's domain.

PhishingAuthentication