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=Rejectwill still be parsed asreject(case-insensitive). - A typo like
policy=rejectwill not parse, because the tag name isp, notpolicy. The parser will not find aptag, the value will default tonone, and you will get thep=nonefinding 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, orrejectfalls through to theelsebranch and is treated asp=nonefrom 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=rejectis the only DMARC policy Cathedra reports aspass. Everything else is warning or error.p=noneis severity high even though it is a warning — a domain stuck there has no real protection.p=quarantineis 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=rejectis parsed as missingp, which meansp=nonefinding. Read the tag names.
Cathedra alignment: DmarcRecordCheck.php — direct mapping of p= value to status, severity, and finding text
