dmarc: implement RFC 7489 org-domain fallback and RFC 9091 PSD DMARC

DMARC lookup now follows the full RFC 7489 §6.6.3 fallback chain: exact
From domain → organizational domain (eTLD+1 via PSL) → public suffix
domain (RFC 9091, only when psd=y is present). DNS errors abort
immediately without triggering fallback; NXDOMAIN and missing v=DMARC1
records do trigger it. The found domain is exposed in the new
DMARCRecord.domain field for reporting purposes.

Also promote getOrganizationalDomain to a package-level function so both
HeaderAnalyzer and DNSAnalyzer can share it, and fix pre-existing
rbl_test.go compilation errors and stale score expectations.

Closes: #98
This commit is contained in:
nemunaire 2026-05-18 15:33:27 +08:00
commit 1516991057
7 changed files with 295 additions and 57 deletions

View file

@ -3,9 +3,15 @@
interface Props {
dmarcRecord?: DmarcRecord;
fromDomain?: string;
}
let { dmarcRecord }: Props = $props();
let { dmarcRecord, fromDomain }: Props = $props();
const isFallback = $derived(
!!dmarcRecord?.domain && !!fromDomain && dmarcRecord.domain !== fromDomain,
);
const isPsdFallback = $derived(isFallback && !dmarcRecord?.domain?.includes("."));
// Helper function to determine policy strength
const policyStrength = (policy: string | undefined): number => {
@ -52,6 +58,24 @@
{/if}
</div>
<!-- Fallback domain notice -->
{#if isFallback}
<div class="mb-3">
<strong>Record found at:</strong>
<code>{dmarcRecord.domain}</code>
<div class="alert alert-info mt-2 mb-0 small">
<i class="bi bi-info-circle me-1"></i>
No DMARC record exists for <code>{fromDomain}</code>. The record above was
inherited from
{#if isPsdFallback}
the Public Suffix Domain <code>{dmarcRecord.domain}</code> per RFC 9091.
{:else}
the organizational domain <code>{dmarcRecord.domain}</code> per RFC 7489.
{/if}
</div>
</div>
{/if}
<!-- Policy -->
{#if dmarcRecord.policy}
<div class="mb-3">

View file

@ -165,7 +165,10 @@
{/if}
<!-- DMARC Record -->
<DmarcRecordDisplay dmarcRecord={dnsResults.dmarc_record} />
<DmarcRecordDisplay
dmarcRecord={dnsResults.dmarc_record}
fromDomain={dnsResults.from_domain}
/>
<!-- BIMI Record -->
<BimiRecordDisplay bimiRecord={dnsResults.bimi_record} />