first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
# dmarc-checker
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Implements DMARC (RFC 7489) policy evaluation on top of an upstream
SPF result and DKIM verification. Looks up the `_dmarc.<from-domain>`
TXT record through an injectable resolver, parses its policy and
alignment tags, then determines whether at least one of SPF or DKIM
passes **and** aligns with the From header's organisational domain.
## Features
- `parseDmarcRecord` returns a strongly-typed `DmarcRecord` with all
standard tags (`p`, `sp`, `adkim`, `aspf`, `pct`, `rua`, `ruf`).
- `checkDmarc` accepts pre-computed SPF and DKIM outcomes so callers
can compose authentication pipelines without re-running expensive
DNS or crypto work.
- Both alignment modes — strict (exact domain match) and relaxed
(organisational-domain match) — are supported.
- DNS resolver is async and injectable; the package itself never
calls into `dns.*` directly.
- Records `email_processing.dmarc.result_total` attributed by
`result` for every outcome.
## Usage: Basic
```ts
import { checkDmarc } from "@suaveplan/email-processing/dmarc";
const out = await checkDmarc(fromDomain, spfResult, dkimResult, async (d) => {
const recs = await dns.resolveTxt(d);
return recs.map((p) => p.join(""));
});
if (out.result === "fail" && out.policy === "reject") {
rejectMessage();
}
```
## Usage: Advanced
```ts
import { checkDmarc, parseDmarcRecord } from "@suaveplan/email-processing/dmarc";
const recs = await dnsResolver(`_dmarc.${domain}`);
const dmarcText = recs.find((r) => r.startsWith("v=DMARC1")) ?? "";
const parsed = parseDmarcRecord(dmarcText);
const aggregateUri = parsed?.reportUri;
if (aggregateUri) {
await scheduleReport({ domain, uri: aggregateUri });
}
const result = await checkDmarc(domain, spf, dkim, dnsResolver);
metrics.recordDmarc(domain, result.result, result.policy);
```
## API Reference
- `DmarcPolicy` — `"none" | "quarantine" | "reject"`.
- `DmarcAlignment` — `"strict" | "relaxed"`.
- `DmarcResult` — `"pass" | "fail" | "none"`.
- `DmarcRecord` — full parsed record.
- `DmarcCheckResult` — `{ result, policy?, spfAligned, dkimAligned, record?, error? }`.
- `parseDmarcRecord(txt)` — strict parser, returns `null` on invalid
input.
- `checkDmarc(fromDomain, spf, dkim, resolver)` — async orchestrator.
## Implementation Notes
The DMARC TXT record lookup uses the bare `_dmarc.<domain>` form per
RFC 7489 §6.6.3 (organisational-domain fallback is left to a higher
layer — callers who want PSL-aware fallback should chain a second
`checkDmarc` against the parent domain when the first returns
`result: "none"` with `error: "No DMARC record found"`). The
`reportDmarc` helper centralises OTel emission so every return path
records the outcome exactly once.