Your DMARC says p=reject. Then sp=none reopens the door.

You read p=reject at the front of your DMARC record and you stop reading. Reasonable. That tag is the headline, it's the one every checker grades on, and it says forgeries get refused. The two tags further down the same line, sp=none and pct=50, are where the record quietly takes most of that back.

This is the trap that survives a casual review. A record at p=reject passes any green-checkmark tool, passes a glance from you, passes a glance from the consultant who set it up. Nobody scrolls to the sp= value because nobody thinks to. And sp=none means the reject policy you just admired applies to exactly one name: the apex. Everything below it inherits a different instruction.

If you're still at p=none, that's a separate and bigger problem, covered in what p=none actually allows. This article is for the domain that did the work, turned on enforcement, and left a hole anyway.

The subdomain you never created

RFC 7489 section 6.3 says a subdomain takes its policy from the sp= tag if one is present, not from p=. So sp=none tells every receiver: for anything under this domain, monitor only, deliver the failures.

Here is what that buys an attacker. They don't forge [email protected], because that's the apex and p=reject would bounce it. They forge [email protected] instead. You never created a billing subdomain. It has no SPF record, no DKIM key, no mail server, nothing. It doesn't need any of that. The receiver looks up the DMARC policy for the subdomain, finds sp=none inherited from your apex record, and applies it: monitor only. The spoofed invoice lands in the inbox, the From address reads as your company, and the recipient has no reason to doubt it. Attackers invent subdomains precisely because sp is the tag people forget, and an invented name has no contradicting records to trip over.

$ dig +short TXT _dmarc.yourdomain.com
dig +short TXT _dmarc.yourdomain.com
"v=DMARC1; p=reject; sp=none; rua=mailto:[email protected]"
The apex says reject. sp=none means every subdomain, including ones an attacker invents, is wide open.
The headline tag enforces. The third tag quietly excludes everything below the apex.

When pct turns the policy into a coin flip

The other version of this gap is pct. The tag takes a number from 0 to 100 and applies your policy to only that share of failing mail. The rest drops down one level of severity: under p=reject; pct=50, half of failing mail is refused and the other half is merely quarantined, and under a quarantine policy the leftover fraction is delivered.

So pct=50 makes enforcement a coin flip, and a spoofer doesn't fight a coin flip, they just send twice. Two attempts, and one is statistically through. Three attempts and it's a near certainty. pct was meant for the cautious rollout, a way to apply p=reject to a slice of traffic before committing, exactly the staged approach described in the p=none article. Left in place permanently it's a permanent gap. (DMARCbis, the in-progress revision, makes pct historic, but receivers still honor it widely today, so a low pct is a live hole right now, not a future one.)

What the record looks like

v=DMARC1; p=reject; sp=none; pct=50
Same p=reject in both. The difference is the two tags after it.

The fix is small and it's in DNS, not in your mail flow. Set sp=reject, or sp=quarantine if you want a softer landing on subdomains while you watch reports. Set pct=100, or just delete the pct tag entirely, since DMARCbis and current practice both treat an absent pct as 100. Keep rua= so the aggregate reports keep coming and you can see what's failing and from where.

A word of care on sp. If you run legitimate mail from real subdomains, confirm each one passes SPF and DKIM before you tighten sp, the same homework you'd do at the apex. The goal is to close the door on invented subdomains without slamming it on your own.

SurfaceCheckr reads the _dmarc TXT record straight from public DNS, the same plain lookup you'd run by hand, nothing crafted and no mail involved. The difference is it parses the whole record instead of stopping at the first tag. It flags sp=none as the subdomain gap it is, and it flags any pct under 100, the two parts a quick read sails right past. It can't read your aggregate reports for you, and it isn't a pentest. It will tell you in seconds whether your p=reject actually reaches the names an attacker would forge. Run a free scan and check the tags you stopped reading. If SPF is also on your list, start with the SPF record and come back here.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.