Hermes glaubt dem dmarc=pass des Absenders nicht mehr


Du hängst Hermes an dein Postfach: Der tägliche Digest kommt an, er liest, fasst zusammen und merkt sich Dinge. Nach einem Upgrade kommt gar nichts mehr an — nicht einmal von Absendern auf deiner Allowlist. Der einzige Hinweis ist eine einzige WARNING-Zeile im Log, die eine Einstellung nennt, die du nie konfiguriert hast.

Das Problem ist die Frage „wer hat das tatsächlich gesendet?“. Hermes prüft den Header Authentication-Results, um die Absenderidentität festzustellen, und dieser Header ist schlichter Text, den ein Absender selbst schreiben kann. Wenn dein empfangender Server seine eigene Version nicht stempelt, sitzt eine injizierte Zeile obenauf, und Hermes behandelte sie früher als maßgeblich. Der Fix ist unmissverständlich: kein Pin, kein Vertrauen.

Wie die Fälschung funktionierte

Authentication-Results ist der Header, der für „diese Nachricht hat SPF / DKIM / DMARC bestanden“ bürgt. Normalerweise fügt dein empfangender Server ihn hinzu, nachdem die Nachricht angekommen ist — gerade weil der Server die Verifizierung durchgeführt hat, ist sein Urteil das lesenswerte.

Aber es ist ein normaler Header. Ein Absender kann seine eigene Zeile einfügen: attacker.self; dmarc=pass header.from=allowed.example. Wenn du einen selbst gehosteten MTA betreibst, der keine eigene authserv-id stempelt (im Stil von OpenDKIM / OpenDMARC), bleibt diese Zeile obenauf. Das alte Verhalten von Hermes las den obersten Header, unabhängig davon, wer behauptete, ihn geschrieben zu haben, also:

  • der Angreifer fälscht From: passend zu einer Adresse auf deiner Allowlist;
  • er fügt seine eigene dmarc=pass-Zeile hinzu;
  • Hermes hält den Absender für authentifiziert und stellt zu.

Die überarbeitete _verify_sender_authentication akzeptiert nur zwei Ergebnisse — sie liest den obersten Authentication-Results und verlangt, dass seine authserv-id exakt mit deiner übereinstimmt:

if not authserv_id:  # ohne einen Pin kann der oberste Header einer sein, den der Absender geschrieben hat
    return False, _MISSING_AUTHSERV_REASON
if clauses[0].strip().lower() != authserv_id:
    return False, _UNTRUSTED_AUTHSERV_REASON

Zwei Nachbarn wurden ebenfalls verschärft: Zwei dmarc-Ergebnisse in einem Header erzeugen jetzt ambiguous dmarc result statt einer Vermutung, und ein dmarc=pass muss jedes header.from mit der von Hermes geparsten From:-Domain in Übereinstimmung bringen.

Was sich nach dem Upgrade tatsächlich ändert

Das ist eine Verhaltensänderung, also ist der Blast-Radius wichtig:

  • Wer betroffen ist: Deployments mit aktivierter Absenderauthentifizierung und ohne gesetzten Pin. Die Absenderauthentifizierung ist standardmäßig aktiv, einschließlich Open-Access-Setups wie EMAIL_ALLOW_ALL_USERS / GATEWAY_ALLOW_ALL_USERS.
  • Was passiert: Jede Nachricht, die ein authentifiziertes From: benötigt, wird verworfen, auch Allowlist-Absender, bis der Betreiber eines von zwei Dingen tut — EMAIL_AUTHSERV_ID setzen (oder den Config-Key platforms.email.authserv_id), oder explizit aus der Absenderauthentifizierung aussteigen.
  • Was du siehst: Jedes ungepinnte Konto loggt beim Verbinden eine WARNING, die besagt, dass die authserv-id nicht konfiguriert ist und dass Hermes sich weigert, Authentication-Results zu vertrauen, plus beide Notausgänge (authserv-id setzen oder require_authenticated_sender: false / EMAIL_TRUST_FROM_HEADER=true setzen). Die Warnung erscheint auch, wenn der erste Verbindungsversuch eines Kontos fehlschlägt und der Reconnect-Watcher es später hochbringt.
  • Wer nicht betroffen ist: gepinnte Deployments und Deployments, die bereits ausgestiegen waren. Sie verhalten sich exakt wie zuvor.

Den zu pinnenden Wert finden

Raten bringt nichts. Öffne eine Nachricht, die dein Server zugestellt hat, sieh dir die Rohquelle an („Original anzeigen“ in Gmail), finde den obersten Authentication-Results-Header und nimm das Token vor dem ersten Semikolon.

  • Bei Gmail ist das mx.google.com;
  • wenn sich das Token zwischen Nachrichten ändert, ist es ein Hostname pro Relay. Die richtige Maßnahme ist dann, deinen MTA eine stabile authserv-id stempeln zu lassen (OpenDKIMs AuthservID, das Äquivalent in OpenDMARC) und diese zu pinnen.

Beide Konfigurationen funktionieren; die Umgebungsvariable ist für Container freundlicher:

# .env oder Container-Umgebung
EMAIL_AUTHSERV_ID=mx.google.com
# config.yaml
platforms:
  email:
    authserv_id: mx.google.com
    # Standard-Strict-Mode beibehalten; nur deaktivieren, wenn du das Fälschungsrisiko akzeptierst
    require_authenticated_sender: true

Der Wert wird auf Kleinschreibung normalisiert, Groß-/Kleinschreibung spielt also keine Rolle, aber die Domain muss übereinstimmen — erweitere sie nicht auf eine übergeordnete Domain, um Tipparbeit zu sparen.

Wenn du heute wirklich wieder Mail durchbekommen musst

platforms.email.require_authenticated_sender: false (oder EMAIL_TRUST_FROM_HEADER=true) zu setzen lässt Hermes wieder unauthentifizierte Absender akzeptieren. Das ist ein bewusstes Downgrade: Auf einem selbst gehosteten MTA bedeutet es, dass jeder einen Allowlist-Absender fälschen kann. Die bessere Alternative ist eine stabile authserv-id auf deinem MTA — einmal konfigurieren und nicht mehr daran denken.

Für andere Identitätsfälschungs-Fixes aus derselben Familie siehe die drei Spoofing-Fixes für Absenderidentität; der Rest der E-Mail-Plattformkonfiguration steht in der Hermes-Befehlsreferenz.

Kurz zusammengefasst

  • Authentication-Results ist ein Header, den der Absender schreiben kann; auf einem selbst gehosteten MTA ohne Stempel kann er nach oben sortieren;
  • Hermes verlangt jetzt eine exakte authserv-id-Übereinstimmung und verwirft Mail, die einen authentifizierten Absender benötigt, bis du eine setzt, und loggt eine WARNING;
  • kopiere das Token vor dem ersten Semikolon des obersten Authentication-Results (Gmail: mx.google.com) in EMAIL_AUTHSERV_ID / platforms.email.authserv_id;
  • der Ausstieg ist require_authenticated_sender: false oder EMAIL_TRUST_FROM_HEADER=true, und er bedeutet, das Fälschungsrisiko zu akzeptieren.