Skip to content

DMARC is p=none, so scripthammer.com can be spoofed — and it sends payment receipts #822

Description

@TortoiseWolfe

⛔ 2026-08-21 — do NOT raise enforcement yet. Two blockers, one of them undocumented until now.

1. The evidence step cannot be completed. Step 1 below says "read a few days of rua
reports". Searching admin@'s mailbox for the last 30 days returns exactly one aggregate
report — Google, Report-ID: 9866291112064302175, covering a single day (2026-08-16→17). One
day of reports from one receiver is not a basis for enforcing a policy on a domain that sends
payment receipts.

2. #368 is open, and it is a hard dependency this ticket does not name. Replies to
admin@scripthammer.com still leave through a personal Gmail. Gmail's outbound is not in
the root SPF (which lists only Cloudflare, for inbound) and a plain send-as does not DKIM-sign
as d=scripthammer.com. So those replies fail DMARC alignment today — harmlessly, because
p=none. Under p=quarantine the maintainer's own replies start going to spam.

That is the concrete failure a pct=25 rollout is supposed to surface slowly, except here it
is known in advance and free to avoid: close #368 first, then let reports accumulate, then
raise.

Live DNS re-verified 2026-08-21 — all four records present and correct: DMARC p=none
with rua to admin@, root SPF including Cloudflare, resend._domainkey DKIM key present,
MX on route1/2/3.mx.cloudflare.net. Nothing has drifted since 2026-08-19.

What was delivered instead: the assertion this ticket asks for under "Not automatable from
this repo". scripts/ci/check-mail-policy.mjs now checks the LIVE zone against an INTENDED
policy declared in the repo, so raising p= becomes a one-line reviewable change that CI
enforces — rather than an undocumented dashboard edit nothing records. It fails on a missing
DMARC record, a policy that does not match the declared intent, a missing DKIM key, a missing
SPF, and a missing MX. That last one matters for #881: if inbound routing breaks, admin@
stops receiving and the security policy silently starts dropping vulnerability reports again.

_dmarc.scripthammer.com is p=nonemonitor-only. Nothing is enforced. Anyone can
send mail claiming to be @scripthammer.com today and receivers will deliver it; DMARC
only reports it afterwards.

That matters more here than for a brochure domain: this one sends payment receipts and
signup confirmations
. A spoofed "your payment failed, re-enter your card" from your own
domain passes every visual check a customer makes.

Verified DNS state, 2026-08-19 (read over DoH)

record value role
_dmarc.scripthammer.com TXT v=DMARC1; p=none; rua=mailto:admin@scripthammer.com no enforcement
scripthammer.com TXT v=spf1 include:_spf.mx.cloudflare.net ~all Cloudflare
scripthammer.com MX route1/2/3.mx.cloudflare.net inbound Email Routing
send.scripthammer.com TXT v=spf1 include:amazonses.com ~all Resend's sending subdomain (SES underneath)
resend._domainkey TXT RSA public key present Resend DKIM-signs d=scripthammer.com

The authentication itself is correct, and that is worth stating because the root SPF
looks wrong at a glance: it lists only Cloudflare, which handles inbound. Resend does not
need to be there — it sends from the send. subdomain with its own SPF and DKIM-signs
with d=scripthammer.com, which aligns with the From:. DMARC passes if either aligns, so
transactional mail authenticates. Do not "fix" the root SPF by adding Resend to it.

What to do

The standard path is nonequarantinereject, and the daily aggregate reports are
the evidence for each step. Unlike some monitoring, these reports will genuinely tell us
something
, because real mail is flowing through Resend right now.

  1. Read a few days of rua reports and confirm every legitimate sender aligns — Resend,
    and anything else that turns out to be sending (that is what the reports are for).
  2. Move to p=quarantine; pct=25, watch, then raise pct.
  3. Move to p=reject.

Also worth adding at step 1: ruf= (forensic reports) is absent, and sp= (subdomain
policy) is unset, so send.scripthammer.com inherits whatever the root becomes.

Not automatable from this repo

The DMARC record lives in Cloudflare DNS, not in this tree — the same shape as the cache
rules in #635, where nothing in the repo would notice if the config were lost. If this is
tightened, it is worth a smoke.yml-style assertion that the published policy is still
what we intend, for the same reason check-cache-headers.mjs exists.

Prompted by a routine Google aggregate report landing in admin@ on 2026-08-19 — which is
the system working, not a fault.

Related: #781 (registrar still Squarespace), #368 (admin@ send-as), #635 (Cloudflare config
living outside the repo).

Metadata

Metadata

Assignees

No one assigned

    Labels

    blocked:ownerNeeds credentials, money, a legal call, or a decision only the owner can makeenhancementNew feature or requestpriority:p2Medium — schedule (feature gaps, partial implementations)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions