Skip to content

feat: add RFC 9989 t= tag support and a config knob to ignore pct= - #434

Merged
thegushi merged 1 commit into
trusteddomainproject:developfrom
thegushi:feat/dmarcbis-t-tag
Jun 22, 2026
Merged

feat: add RFC 9989 t= tag support and a config knob to ignore pct=#434
thegushi merged 1 commit into
trusteddomainproject:developfrom
thegushi:feat/dmarcbis-t-tag

Conversation

@thegushi

Copy link
Copy Markdown
Collaborator

Summary

  • Adds parsing and a fetch accessor (opendmarc_policy_fetch_t()) for the t (test mode) tag RFC 9989 introduces as the replacement for most of pct's former role (Appendix A.6).
  • t=y steps the enforced policy down one level (reject -> quarantine, quarantine -> none) per S 3.2.7, rather than suppressing enforcement outright the way pct=0 does. This is computed into a new enforce_policy local rather than overwriting policy, since policy is read again later to decide failure-report eligibility, and RFC 9989 is explicit that t= "does not affect the generation of DMARC reports."
  • pct= sampling is left completely unchanged by default: real published DMARC records actively use pct=0 and pct=100 today (Shopify, Oracle, Akamai, Salesforce, several .edu domains, per a local Umbrella top-1M survey), and dropping support would start enforcing on traffic those domains don't yet want enforced.
  • New boolean DMARCbisIgnorePct (default false) ignores pct= entirely, equivalent to treating every record as pct=100, for operators who want full RFC 9989 compliance now.

Out of scope here: wiring t=/np= into the historyfile, DB schema, and opendmarc-reports' XML output for RFC 9990 aggregate reporting -- that's a separate, larger task.

Test plan

  • make check in libopendmarc/ -- 13/13 pass, including new t= parse/fetch coverage (valid y/n, invalid value, default-when-absent)
  • Clean build of libopendmarc, opendmarc, and opendmarc-check with no new compiler warnings

Adds parsing and a fetch accessor for the "t" (test mode) tag RFC 9989
introduces as the replacement for most of "pct"'s former role
(Appendix A.6). t=y steps the enforced policy down one level (reject ->
quarantine, quarantine -> none) per S 3.2.7, rather than suppressing
enforcement outright the way pct=0 does.

The step-down is computed into a new local, enforce_policy, instead of
overwriting policy: policy is read again later to decide whether to
generate a failure report, and RFC 9989 is explicit that t= "does not
affect the generation of DMARC reports." Overwriting it in place would
have silently changed failure-report eligibility for stepped-down
messages.

pct= sampling (random() % 100 < pct, gating RejectFailures and
HoldQuarantinedMessages) is left completely unchanged by default: real
published DMARC records actively use pct=0 and pct=100 today (e.g.
Shopify, Oracle, Akamai, Salesforce, several .edu domains, per a local
Umbrella top-1M survey), and dropping support for that would start
enforcing on traffic those domains do not yet want enforced. A new
boolean, DMARCbisIgnorePct (default false), ignores pct= entirely,
equivalent to treating every record as pct=100, for sites that want
full RFC 9989 compliance now.

Out of scope here: wiring t=/np= into the historyfile, DB schema, and
opendmarc-reports' XML output for RFC 9990 aggregate reporting -- that
is a separate, larger task.
@thegushi
thegushi merged commit 9b759b2 into trusteddomainproject:develop Jun 22, 2026
2 checks passed
@thegushi
thegushi deleted the feat/dmarcbis-t-tag branch June 22, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant