Decoding SPF Failures in M365: How EOP Treats SoftFail vs. PermError
A softfail might let a phish land, but a broken SPF record with a PermError creates a high-risk blind spot in Microsoft's Exchange Online Protection.

An analyst sees it every day: a credential harvesting link sitting in a user's inbox, delivered straight past the filters. The headers show `spf=softfail`, and the incident response ticket gets a shrug. 'SPF is a weak signal,' someone says. They're not wrong, but they're not entirely right, either.
The way that Microsoft's Exchange Online Protection (EOP) handles Sender Policy Framework (SPF) is more nuanced than a simple pass/fail. The distinction between a `~all` (SoftFail) and a `-all` (Fail) is well-understood. What's dangerously misunderstood is how EOP's internal scoring engine treats a `PermError` or a `TempError`, and why a broken SPF record can be far worse than a weak one.
This isn't just academic. Your domain's SPF record is a direct instruction to the world on how to handle mail claiming to be from you. When that instruction is ambiguous or malformed, EOP makes a judgment call—and that call can directly enable a spearphishing attack by blinding the very anti-spoofing logic designed to stop it.
More Than a Verdict: How EOP Scores SPF Signals
Every email gateway that evaluates SPF, as defined in RFC 7208, produces a verdict. But that verdict isn't the end of the story; it's the input. For a massive, multi-tenant system like EOP, that simple verdict is fed into a complex machine learning model that calculates a Spam Confidence Level (SCL). The final action—deliver to inbox, junk, or quarantine—depends on that SCL, not just the raw SPF result.
The Baseline Verdicts: Pass, Fail, Neutral, and None
Let's get the simple ones out of the way. An `spf=pass` is a strong positive signal. EOP sees that the sending IP is authorized and significantly lowers the chance of marking the message as spam, assuming other signals are clean. An `spf=fail` from a record with `-all` is a powerful negative signal. EOP's anti-spoofing logic weighs this heavily, drastically increasing the SCL and often flagging the message for spoofing (`SFV:SPOOF`). `Neutral` (`?all`) and `None` (no SPF record found) are what they sound like: neutral. EOP gets no signal either way and must rely entirely on other factors like IP reputation, DKIM, DMARC, and content analysis.
The Ambiguous Failures: SoftFail, TempError, and PermError
Here's where the wheels come off. A `SoftFail` (`~all`) is explicitly defined by RFC 7208 as a state for transitioning or testing. It's a statement that says, "The sender is probably not authorized, but I'm not confident enough to say you should reject it." EOP honors that intent. It treats a `SoftFail` as a minor negative signal. It nudges the SCL score up slightly but is easily overridden by other positive signals, like a clean sending IP or benign content. This is why so many low-effort phishing emails sail right through.
A `TempError` indicates a transient DNS problem, like a timeout during a lookup. EOP will typically treat this as inconclusive, similar to a `SoftFail`, but may retry. If the error persists for a sender, it contributes to a higher SCL over time, but it won't cause an outright block on its own. The real danger, however, is the `PermError`.
The SoftFail Fallacy: Why `~all` Is a Phisher's Friend
Many organizations use `~all` because they believe it's a safe default, especially when wrestling with third-party senders. They see it as a compromise between the strictness of `-all` and the permissiveness of `?all`. This is a dangerous fallacy. A `SoftFail` is not a weak 'fail'; for practical purposes in EOP, it's closer to a 'neutral with a frown'.
Imagine a business email compromise (BEC) attempt impersonating your CFO. The attacker sends from a newly provisioned virtual server at a major cloud provider. The provider's IP isn't in your SPF record. Because your record ends in `~all`, receiving mail servers like EOP will see an `spf=softfail`. The attacker knows this. They craft the email body to be free of obvious spam triggers, and maybe they even pass DKIM for their own throwaway domain. EOP's filter looks at the signals: SPF is a soft fail (minor penalty), DKIM passes (for an irrelevant domain), IP reputation is okay, content is clean. The resulting SCL is too low to trigger the junk filter, and the email lands.
Authentication-Results: spf=softfail (sender IP is 198.51.100.23) smtp.mailfrom=yourcompany.com; dkim=pass (signature was verified) header.d=attackerdomain.com;
The SoftFail gives you a false sense of security. It populates your DMARC reports with failures, making it look like you're catching things. But in reality, it provides almost no preventative value against moderately sophisticated attacks within EOP. Without DMARC alignment and a policy of `p=quarantine` or `p=reject`, the `SoftFail` does nothing to stop the message from being delivered.
The PermError Paradox: A Broken Record Is Worse Than None
This is the most counter-intuitive part of SPF evaluation. A Permanent Error (`PermError`) sounds severe, and it is. It means your SPF record is broken. This can happen for two primary reasons: a syntax error in the record itself, or exceeding the 10 DNS lookup limit defined in the RFC. The latter is shockingly common, as admins add `include:` mechanisms for various SaaS platforms without realizing the nested lookups blow past the ceiling.
How PermError Blinds Anti-Spoofing and DMARC
Your intuition might say a `PermError` should be treated as a hard `Fail`. It’s not. When EOP attempts to evaluate a broken record, it can't reach a conclusion. It can't determine if the IP is authorized or not. So, what does it do? It effectively gives up. The SPF check results in an inconclusive state, which for DMARC evaluation purposes, is treated like `None`.
Let's be clear about the operational stake. If your DMARC policy is `p=reject`, you are relying on an SPF `Fail` (or DKIM `Fail`) to trigger that rejection. If your SPF record produces a `PermError`, there is no `Fail` verdict. SPF's contribution to the DMARC check is nullified. The check doesn't pass, but crucially, it doesn't fail either. This means EOP cannot enforce your DMARC policy based on the SPF result. You've configured rejection, but a syntax error has silently downgraded your security to doing nothing at all.
The Silent Failure Mode
This failure is silent because mail still flows. You won't get NDRs or alerts that your SPF record is broken. Unauthorized mail that *should* be failing SPF checks will be delivered to user inboxes, completely bypassing the DMARC policy you painstakingly configured. Admins looking at their DNS zone see what appears to be a strong `-all` record, not realizing it's syntactically invalid or over the lookup limit for every receiver on the internet. It’s a self-inflicted wound that cripples your domain's defenses.
Don't Guess, Check: Simulating EOP's SPF Perspective
The cardinal sin of DNS management is making changes without validation. With SPF, the consequences are particularly severe. Adding a single `include:`, like for a new marketing platform, can be the straw that breaks the camel's back if it pushes your lookup count from 9 to 11.
Before you ever push an SPF change to production, you must use an SPF checker. Don't just look for a simple 'valid' checkmark. A good tool will visualize the entire DNS lookup chain for you. It should show every `include`, every `a`, every `mx`, and every `ptr` mechanism, counting them up so you can see if you're under the 10-lookup limit.
An SPF record that is syntactically correct but exceeds the lookup limit is not a valid record. It's a landmine.
This simulation shows you what remote MTAs like EOP will see. It reveals the nested complexity hidden behind your cloud vendors' SPF records. Microsoft's own record (`spf.protection.outlook.com`) is a masterclass in compressed, nested lookups. Your vendors may not be so careful. Simulating the check is the only way to be sure that your `-all` will actually produce a `Fail` and not a `PermError`.
Forensics in the Header: Decoding the X-Forefront-Antispam-Report
When you need to prove why a message was delivered, you need to go to the source: the email headers. In Microsoft 365, the `X-Forefront-Antispam-Report` header is the internal monologue of EOP's scanning engine. Learning to read it is a non-negotiable skill for any security analyst working in an M365 environment.
This header is a dense collection of key-value pairs separated by semicolons. It tells you exactly how EOP scored the message and why it took the action it did.
X-Forefront-Antispam-Report: CIP:198.51.100.23;CTRY:US;LANG:en;SCL:5;SRV:SPM;IPV:NLI;SFV:SPOOF;H:attacker.com;PTR:server.attacker.com;CAT:SPM;SFTY:9.25; — Example EOP Header
Let's decode the critical fields. `SCL` is the Spam Confidence Level; a '5' means it was marked as spam and sent to Junk. An `SCL` of '1' means it went to the inbox. `SFV:SPOOF` is a huge indicator; it means EOP's explicit anti-spoofing intelligence identified this as an impersonation attempt, likely due to a DMARC or SPF `Fail`. If you see a message with `spf=softfail` in the `Authentication-Results` header but an `SFV:NSPM` (non-spam) and `SCL:1` in the Forefront header, you have your smoking gun. The weak SPF signal wasn't enough to trip the filter.
By correlating the SPF result with the `SCL` and `SFV` values, you can definitively trace the impact of your SPF configuration on mail delivery. A `PermError` will often correlate with a lower `SCL` and a missing `SFV:SPOOF` value, proving that the broken record prevented the anti-spoofing engine from doing its job.
The takeaway
Stop thinking of SPF as a binary control. The specific state of your SPF record—working and strict, working and weak, or broken—has a direct and predictable impact on how your mail is treated by Microsoft 365. A `SoftFail` is an invitation for trouble, but a `PermError` is a declaration of surrender. It silently deactivates your DMARC policy and gives attackers a clear path to the inbox.
Your SPF record is either actively defending your domain with a validated, working `-all` mechanism, or it's a liability. There is no safe middle ground. Analyze your headers, simulate your changes, and treat your SPF record with the seriousness it deserves. Tools that can parse and correlate these complex headers, like MailSleuth.AI, are invaluable for moving from guesswork to concrete evidence during an incident investigation.
We dissect phishing campaigns and email infrastructure so you don't have to.


