Policy Levels and What Cathedra Reports

Policy levels and what Cathedra reports

The p= tag in the DMARC record is the single most important field. It tells receivers what to do when a message fails alignment. Cathedra reads it, classifies it, and produces the finding text you will see in the Examen output.

Memorize the four outcomes. You should be able to look at a DMARC record and predict, before opening Cathedra, exactly which finding will appear.

The four states

DmarcRecordCheck.php produces one of four findings, depending on the policy value or the record’s absence.

State 1 — No DMARC record at all

When dns_get_record('_dmarc.{domain}', DNS_TXT) returns no record beginning with v=DMARC1, Cathedra reports:

Field Value
status error
severity high
finding “No DMARC record found at _dmarc.{domain}. Phishing protection is absent.”
recommendation “Publish a DMARC TXT record. Start with p=none and an rua address to begin receiving reports.”

This is the worst-case finding from the DMARC check. The domain has no policy at all, so receivers have no instruction and will deliver spoofed mail on its merits.

State 2 — p=none

v=DMARC1; p=none; rua=mailto:dmarc@isidoregroup.com

Cathedra reports:

Field Value
status warning
severity high
finding “DMARC policy is p=none. Unauthenticated mail is not blocked or quarantined.”
recommendation “Review aggregate reports (rua) and migrate to p=quarantine, then p=reject.”

p=none is monitoring mode. Receivers do not act on failures, but they do send aggregate reports to the rua address. This is the right starting state for any new DMARC deployment — you watch the reports for several weeks, identify every legitimate sender, fix alignment for each, and only then move to enforcement.

Severity is high even though it is a warning. This is because a domain stuck at p=none long-term has no real DMARC protection, and most domains that get stuck never move past it.

State 3 — p=quarantine

v=DMARC1; p=quarantine; rua=mailto:dmarc@isidoregroup.com

Cathedra reports:

Field Value
status warning
severity medium
finding “DMARC policy is p=quarantine. Unauthenticated mail is sent to spam, not rejected.”
recommendation “Consider upgrading to p=reject once reporting is stable.”

p=quarantine is the staging ground before p=reject. Failing messages land in the recipient’s spam folder, which means they are not delivered to the inbox but can be retrieved if a user goes looking. This protects against phishing in practice, because real users do not fish things out of spam — but it leaves the door open enough that legitimate senders you missed during p=none will surface in user complaints rather than disappearing entirely.

The finding stays a warning at medium severity. You are most of the way to a real DMARC posture, but not all the way.

State 4 — p=reject

v=DMARC1; p=reject; rua=mailto:dmarc@isidoregroup.com

Cathedra reports:

Field Value
status pass
severity —
finding “DMARC policy is p=reject. Unauthenticated mail will be rejected.”
recommendation —

This is the only DMARC policy that produces a pass from Cathedra. Failing messages are rejected at SMTP time — the receiving server returns a 5xx code, the sending server stops trying, and the message never lands anywhere. From a phishing-protection perspective, this is the outcome.

When you see pass on the DMARC policy check in Cathedra, the domain is at the finish line for this control.

Reading the policy from the record

The parser in DmarcRecordCheck.php is straightforward. It splits the record on semicolons, splits each tag on =, and looks up p. The value is lowercased before comparison.

What this means in practice:

  • A typo like p=Reject will still be parsed as reject (case-insensitive).
  • A typo like policy=reject will not parse, because the tag name is p, not policy. The parser will not find a p tag, the value will default to none, and you will get the p=none finding even though the record looks “complete” at a glance. This trips techs up — read the tag names, not just the values.
  • A value other than none, quarantine, or reject falls through to the else branch and is treated as p=none from a finding perspective. This is technically incorrect per RFC 7489 — the record should be ignored as malformed — but Cathedra is conservative and reports the safer state. Either way, the operational answer is the same: fix the record.

Cheat sheet

Record state Cathedra status Severity Finding
Missing error high “No DMARC record found at _dmarc.{domain}. Phishing protection is absent.”
p=none warning high “DMARC policy is p=none. Unauthenticated mail is not blocked or quarantined.”
p=quarantine warning medium “DMARC policy is p=quarantine. Unauthenticated mail is sent to spam, not rejected.”
p=reject pass — “DMARC policy is p=reject. Unauthenticated mail will be rejected.”

Commit this table. You will reference it during every Examen run.

What you should remember

  • p=reject is the only DMARC policy Cathedra reports as pass. Everything else is warning or error.
  • p=none is severity high even though it is a warning — a domain stuck there has no real protection.
  • p=quarantine is severity medium — most of the way home, but not the end state.
  • Missing DMARC is error / high. Phrasing: “No DMARC record found at _dmarc.{domain}. Phishing protection is absent.”
  • Tag names must be correct. policy=reject is parsed as missing p, which means p=none finding. Read the tag names.

Cathedra alignment: DmarcRecordCheck.php — direct mapping of p= value to status, severity, and finding text