Send "none" instead of "pass" as the DMARC report disposition #76

Merged
jcoffey-dev merged 1 commits from fix/dmarc-disposition-compat into main 2026-09-28 05:48:23 +00:00
Owner

Every DMARC aggregate report we send to Cloudflare's report intake (*@dmarc-reports.cloudflare.net) bounces with 555 5.7.1 invalid_report_schema: 13 of 13 since 2026-09-20. Other report services accept the same reports.

Cause

Bisected against the live endpoint using one of our own zones with Cloudflare DMARC Management switched on for the test. Each variant was sent by direct SMTP, so the verdict came back during the SMTP session:

Variant Result
Our report as the server writes it 555
Without <np>, <discovery_method>, <testing> 555
Without the RFC 9990 namespace 555
Without <version> / without <fo> 555
With <pct>100</pct> added 555
A classic RFC 7489-style report (control) 250
Our report with <disposition>pass</disposition> changed to none 250
RFC 7489 layout with none 250

The only thing Cloudflare rejects is the pass value RFC 9990 added to policy_evaluated/disposition. Our reports otherwise validate against the RFC 9990 schema; Cloudflare is stricter than the RFC.

The two-<spf> cause from the upstream forum report was already fixed by mail-auth 0.13.3 and isn't involved here.

Fix

with_compatible_dispositions changes pass to none just before a report is serialized for sending. none (no action taken) is valid under both RFC 9990 and RFC 7489 and tells the reader the same thing. The stored report keeps pass, so the admin views are unchanged.

Tests

  • New unit test reporting::dmarc::tests::pass_disposition_is_reported_as_none.
  • report_dmarc now expects none for the record that passed.
  • smtp::reporting::* + smtp::management::report: 6 passed (RocksDb, serial).
Every DMARC aggregate report we send to Cloudflare's report intake (`*@dmarc-reports.cloudflare.net`) bounces with `555 5.7.1 invalid_report_schema`: 13 of 13 since 2026-09-20. Other report services accept the same reports. ### Cause Bisected against the live endpoint using one of our own zones with Cloudflare DMARC Management switched on for the test. Each variant was sent by direct SMTP, so the verdict came back during the SMTP session: | Variant | Result | |---|---| | Our report as the server writes it | 555 | | Without `<np>`, `<discovery_method>`, `<testing>` | 555 | | Without the RFC 9990 namespace | 555 | | Without `<version>` / without `<fo>` | 555 | | With `<pct>100</pct>` added | 555 | | A classic RFC 7489-style report (control) | **250** | | **Our report with `<disposition>pass</disposition>` changed to `none`** | **250** | | RFC 7489 layout with `none` | **250** | The only thing Cloudflare rejects is the `pass` value RFC 9990 added to `policy_evaluated/disposition`. Our reports otherwise validate against the RFC 9990 schema; Cloudflare is stricter than the RFC. The two-`<spf>` cause from the upstream forum report was already fixed by mail-auth 0.13.3 and isn't involved here. ### Fix `with_compatible_dispositions` changes `pass` to `none` just before a report is serialized for sending. `none` (no action taken) is valid under both RFC 9990 and RFC 7489 and tells the reader the same thing. The stored report keeps `pass`, so the admin views are unchanged. ### Tests - New unit test `reporting::dmarc::tests::pass_disposition_is_reported_as_none`. - `report_dmarc` now expects `none` for the record that passed. - `smtp::reporting::*` + `smtp::management::report`: 6 passed (RocksDb, serial).
jcoffey-dev added 1 commit 2026-09-28 05:31:31 +00:00
Send "none" instead of "pass" as the DMARC report disposition
ci / build (pull_request) Successful in 16m34s
ci / fork-checks (pull_request) Successful in 52s
e1e8a9aeb0
Cloudflare's DMARC report intake rejects every aggregate report we
send with "555 5.7.1 invalid_report_schema". Bisected against the live
endpoint: the only element it objects to is <disposition>pass</disposition>,
the value RFC 9990 added for mail that passed DMARC under an enforcing
policy. The RFC 9990 namespace, <np>, <discovery_method>, <testing> and
a missing <pct> are all accepted, and a report that differs only in
using "none" there goes through.

"none" (no action taken) is valid under both RFC 9990 and RFC 7489 and
says the same thing to the reader, so reports now go out with it. The
stored report keeps "pass"; only the serialized copy changes.
jcoffey-dev merged commit 7ba9ec9fa0 into main 2026-09-28 05:48:23 +00:00
jcoffey-dev deleted branch fix/dmarc-disposition-compat 2026-09-28 05:48:23 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inbuxa/inbuxa-server#76