• v0.47.0 85e904b975

    0.47.0 Stable

    joeyr released this 2026-08-25 18:45:29 -05:00 | 69 commits to main since this release

    Three defects in how a message's authentication is reported, all of them in
    the direction of a confident wrong answer.
    One was firing on 100% of Gmail
    messages; one would have made any future survey of the ARC chain wrong the same
    way the last one was; the third is a check that was never wired up. A fourth
    turned up while fixing them: the Gmail tenant shipped get_message_security
    with no assessment at all.

    Fixed

    • dmarc_policy_none was reported on every Gmail message, whatever the
      sender publishes.
      decide read (results.dmarc_policy or "none"), which
      collapses absent into p=none. The policy is recovered from iCloud's
      X-DMARC-Policy; Gmail stamps no equivalent, so it is empty for every
      message on that tenant — and the warning then asserted the weakest possible
      policy for senders publishing p=reject. It contradicted
      AuthResults.to_dict on the same payload, which correctly omitted the key.

      Absent and none are now two facts: dmarc_policy_unknown where nothing was
      read, dmarc_policy_none only where a policy was read and is none. The
      authentication block says policy_available: false with a note rather than
      going silent, because an absent key reads as "nothing to say" and the reader
      supplies their own prior.

    • _parse_one read the ARC instance tag as the authserv-id. RFC 8617 puts
      i=N in front of the identity, so on an ARC-Authentication-Results the
      authserv-id is the second clause and every verdict lands a clause out. A
      survey run through it reports "an authserv-id and nothing else" about a
      header full of results — which is how that claim entered mail/origin.py.

      _parse_one takes an arc flag and parse_arc reads those headers
      properly. The flag rather than sniffing for a leading i=: a plain
      Authentication-Results is attacker-controlled until is_trusted has ruled
      on it, and skipping an i=1 there would let a sender write the receiving
      server's own identity into their message.

      origin.py's claim survives the correction, and is now measured with a
      parser that could have disproved it.
      probes/probe_origin_dkim.py over the
      4,602-message Gmail archive: the i=1 record really is i=1; mail.protonmail.ch with no verdicts, 258 of 258. What the old parser also
      hid is the i=2 record sealed by mx.google.com on the same messages,
      carrying dkim, dmarc, spf and arc — Google's verdicts about the
      relay, which read as proof of the origin if the instance number is missed.
      Parser and a raw regex now agree at 258; before the fix the parser said 0.

    • A surviving original DKIM signature was never looked for.
      _relay_signed_from reads AuthResults.dkim only to confirm the relay
      signed From, but the receiving MTA verifies every signature on a message
      — and a forwarder that only adds headers does not break the original
      sender's. Such a signature would make the origin a cryptographic verdict
      rather than the relay's claim, lifting the restriction the module is built
      around.

      _origin_signed now checks for one: passing, not from a relay, and aligned
      with the recovered origin. Origin.verified_by names the signer and
      claimed becomes False.

      It fires on nothing, and that is the finding. 258 of 258 relayed messages
      carry the relay's signature and nothing else — not one aligned original
      signature, and none from any non-relay domain at all. SimpleLogin re-signs
      what it forwards. So the claim/verdict distinction stands on evidence rather
      than on inference, a gate tiered on origin authentication still has no input,
      and the path is kept because it can only ever raise confidence, only ever on
      a signature the receiving server verified. If a relay stops re-signing, the
      next message says so instead of the next survey.

      provenance deliberately stays "relayed" rather than gaining a
      relayed_verified: the alias index filters on that exact string, and a new
      one would have silently dropped every better-evidenced message out of the
      history mail/aliases.py is built on.

    • Gmail's get_message_security returned no assessment. iCloud has
      carried it since 0.23.0. It is the sentence that stops four green ticks being
      read as "this message is legitimate", and it is also where the relay caveats
      live — that a relayed message's verdicts belong to the forwarder, and that a
      policy read off such a message is the forwarder's. Gmail is the tenant where
      56% of mail is relayed, so it needed it most and had it least. Every other
      key was present and correct, which is why the gap was invisible.

      Under a relay the unknown-policy qualifier is deliberately suppressed: the
      pass being qualified is the relay's, so naming "the policy this domain
      publishes" would point at the wrong domain while sounding like the sender.

    Verified

    • probes/probe_origin_dkim.py, read-only, both tenants, 2026-08-25. Like
      every probe here it is gitignored — they read .env and print real account
      data — so the counts above are a dated record against a named sample, not
      something re-derivable from this repo.
    • Live on real relayed Gmail mail: dmarc_policy_unknown in place of
      dmarc_policy_none, policy_available: false in the authentication block,
      and the assessment present with its relay attribution intact.
    Downloads