Vulnerability analysis · DMARC

OpenDMARC queries two nodes under RFC 7489 and so cannot see an RFC 9989 policy published at an intermediate label [OpenDMARC ≤ 1.4.2, latest release checked 2026-07-30]

Yongzhe Xu, Virginia Tech  ·  yongzhe@vt.edu  ·  2026-07-31

Recorded for completeness and not submitted as a vendor vulnerability. OpenDMARC implements RFC 7489 section 6.6.3 correctly. The gap opens only against verifiers that have moved to the RFC 9989 tree walk, so it is a migration hazard rather than a code defect.

SoftwareOpenDMARC
VendorThe Trusted Domain Project
Sourcehttps://github.com/trusteddomainproject/OpenDMARC
AffectedOpenDMARC 1.4.2, built from the upstream tarball rel-opendmarc-1-4-2. The finding was originally isolated on the Debian package 1.4.2-1+deb12u1 and re-confirmed against pristine upstream. Earlier 1.4.x and 1.3.x releases are expected to share the behavior. Newest published release checked on 2026-07-30.
WhereDMARC policy discovery, the RFC 7489 section 6.6.3 organisational-domain lookup, in place of the RFC 9989 section 4.8 label-by-label tree walk
WeaknessNone (an RFC 7489 versus RFC 9989 divergence, not a defect)
Reachable byNot an attack. The exposure is a deployment consequence: it arises when a domain owner publishes DMARC only at an intermediate label, in anticipation of RFC 9989 adoption.
CVSS v3.1Not applicable
VerificationReproduced 34 of 34 cases in run 20260527T165112-db7f9513, against an RFC 9989 specification modeller as the reference oracle.

1. Overview

This entry is recorded for completeness. It was not submitted as a vendor vulnerability. OpenDMARC 1.4.2 implements RFC 7489 section 6.6.3 correctly. The divergence recorded here shows up only when the same zones are measured against RFC 9989 section 4.8, which replaced the Public Suffix List organizational-domain lookup with a DNS tree walk. Both discovery models are deployed right now, and the gap between them is a migration hazard, not a code defect. The right request to the project is migration tooling.

The two models search different parts of the name. Under RFC 9989 section 4.8 the verifier climbs from the Author Domain one label at a time, asking for a _dmarc record at each level. It stops when it finds a record, when it reaches a label declared psd=y, or when it has spent its lookup budget. Under RFC 7489 section 6.6.3 the verifier queries _dmarc.<author>, and on NXDOMAIN it jumps straight to the organizational domain derived from the Public Suffix List and queries exactly once more.

A DMARC record published at an intermediate label is authoritative to the climbing verifier. OpenDMARC never asks for it.

2. Analysis

OpenDMARC visits two nodes, the Author Domain and the Public Suffix List organizational domain, and no others. Take the Author Domain a.b.c.example.com.

Node queriedRFC 9989 section 4.8 tree walkOpenDMARC (RFC 7489 section 6.6.3)
_dmarc.a.b.c.example.com (the Author Domain)queriedqueried
_dmarc.b.c.example.comqueriednot queried
_dmarc.c.example.comqueriednot queried
_dmarc.example.com (the organizational domain)queriedqueried

The depth-3 test zone puts the policy at an intermediate label and leaves the organizational domain empty.

sender-x.example.com.                  IN SOA ns.lab.test. h.lab.test. 1 60 60 60 60
sender-x.example.com.                  IN MX  10 mx.sender-x.example.com.
sender-x.example.com.                  IN TXT "v=spf1 +all"
_dmarc.sender-x.example.com.           IN TXT "v=DMARC1; p=quarantine"
sub.sender-x.example.com.              IN SOA ns.lab.test. h.lab.test. 1 60 60 60 60
deep.sub.sender-x.example.com.         IN SOA ns.lab.test. h.lab.test. 1 60 60 60 60
default._domainkey.deep.sub.sender-x.example.com. IN TXT "v=DKIM1; k=rsa; p=..."

A DKIM-signed message from alice@deep.sub.sender-x.example.com splits the two verifiers.

VerifierDiscovery pathVerdict
RFC 9989 specification modellerwalks up and finds the policy at _dmarc.sender-x.example.comdmarc=pass
OpenDMARC 1.4.2queries _dmarc.deep.sub.sender-x.example.com, then jumps to _dmarc.example.comdmarc=none

The node carrying the policy is neither the Author Domain nor the Public Suffix List organizational domain, so OpenDMARC skips over it. In the laboratory this reproduced in 34 of 34 cases across the Author Domain shapes exercised.

3. Reproduction

# zone: the policy sits at an intermediate label, and nowhere else
_dmarc.sender-x.example.com.  IN TXT "v=DMARC1; p=quarantine"
# no record at _dmarc.example.com and none at _dmarc.deep.sub.sender-x.example.com

# message
From: alice@deep.sub.sender-x.example.com
  1. Publish the zone above, with the policy only at _dmarc.sender-x.example.com.
  2. Send a DKIM-signed message from alice@deep.sub.sender-x.example.com.
  3. Compare the verdict against an RFC 9989 verifier.
  4. Move the policy to _dmarc.example.com and repeat, as a control.

Expected (RFC 9989 section 4.8): the verifier walks deep.sub.sender-x.example.com, then sub.sender-x.example.com, then sender-x.example.com, finds p=quarantine, and evaluates against it.

Observed: OpenDMARC stamps dmarc=none.

The control run makes both verifiers agree, which shows that the intermediate placement is the only variable.

4. Assessment

Exposure follows from a deployment choice, and it runs one way. A domain owner who prepares for RFC 9989, publishes DMARC at an intermediate tree level, and drops the record at the organizational domain gets two different outcomes from two receivers. Tree-walk verifiers reject the spoof. OpenDMARC-based verifiers see no policy and deliver it.

There is a second effect on measurement. An operator who audits DMARC coverage against one verifier learns nothing reliable about how the policy is applied elsewhere, and the error runs in whichever direction that verifier errs.

This is interoperability decay during a specification migration. It is not a verifier-side exploit. It is worth recording because the migration is under way and OpenDMARC is widely deployed.

5. Remediation

The request to the project is migration tooling, not a defect fix. Add RFC 9989 section 4.8 tree-walk discovery. Gate it behind a configuration directive so that operators pick the discovery model their deployment needs. Document the migration footprint too: the node set each mode consults, and what a domain owner should expect from receivers still running the RFC 7489 model.

Nothing in the current behavior has to change for OpenDMARC to stay conformant to the specification version it declares.

6. Additional notes

Why this is not submitted as a vendor vulnerability. There is no defect to report. RFC 7489 section 6.6.3 prescribes the exact two-node lookup that OpenDMARC performs, and OpenDMARC performs it correctly. The divergence exists because RFC 9989 replaced that lookup, and it lasts as long as both models are deployed. Filing a vulnerability against a faithful implementation of the specification it declares would be wrong. The disclosure went through the same maintainer chain as a migration-tooling request, not as a CVE candidate.

Few domains have made the deployment choice this depends on. A domain that publishes at its organizational domain, which is what almost all of them do today, is unaffected under either model.

One item of laboratory work is recorded as future work: a mode=7489 variant of the case generator. That would assert the same Author Domain shapes against an explicit RFC 7489 verdict, testing OpenDMARC against the specification it implements rather than against the one that replaced it.

7. References