Datenhaufen zu IT und Elektronik.

Schlagwort: RSPAMD

GPT in Rspamd aktivieren: so nutze ich das LLM-Signal im Score

Setup: FreeBSD 14.3, Rspamd 3.12.1, Postfix + Dovecot. Ich lasse bei kniffligen Mails zusätzlich ein LLM draufschauen. Wichtig: GPT ist bei mir nur ein weiterer Sensor im ganz normalen Rspamd-Scoring — keine Allzweckwaffe und kein „hartes Urteil“.

Voraussetzungen

  • Rspamd inkl. GPT-Plugin (ab ~3.12.x im Paket; konfiguriert wird in local.d/gpt.conf).
  • API-Zugang (OpenAI-kompatibel oder eigener Endpunkt).
  • Grundverständnis zu Rspamd-Metrics/Actions (Reject/Add-Header/Greylist).

OpenAI API Key erstellen: Melde dich auf der Developer-Plattform an, öffne die Seite API Keys und klicke auf Create new secret key. Lege bei Bedarf Berechtigungen fest oder arbeite mit projektbasierten Keys. Kopiere den Key einmalig und bewahre ihn sicher (root-only) auf – bitte nicht teilen. Nutzung/Kosten siehst du im Usage-Dashboard.

Mein gpt.conf

Ich halte die Konfiguration bewusst nüchtern — genug, um robuste Labels zu bekommen, aber ohne Schnickschnack:

# local.d/gpt.conf (Auszug)
enabled = true;
type = "openai";
model = "gpt-4o-mini";
api_key = "GEHEIMER-KEY";

model_parameters {
  gpt-4o-mini {
    max_tokens = 160;
    temperature = 0.0;
  }
}

timeout = 10s;
allow_ham = true;
allow_passthrough = false;
json = false;
reason_header = "X-GPT-Reason";

input = "text";
min_words = 1;
max_size = 256k;

symbols_to_except {
  RCVD_IN_DNSWL_MED = -0.1;
  RCVD_IN_DNSWL_HI  = -0.1;
  DWL_DNSWL_MED     = -0.1;
  WHITELIST_RECP_ADDR = -0.1;
  GREYLIST = 0; GREYLIST_CHECK = 0; GREYLIST_SAVE = 0;
  RCPT_IN_SPAMTRAP = 0; SPAMTRAP = 0; SPAMTRAP_ADDR = 0;
  RCVD_VIA_SMTP_AUTH = 0; LOCAL_CLIENT = 0; FROM_LOCAL = 0;
}

Was bedeutet das?!

  • model = gpt-4o-mini: flott & günstig, deterministisch per temperature = 0.0.
  • allow_ham = true: GPT darf „HAM“ melden (kleines, positives Signal).
  • allow_passthrough = false: Bei Fehlern (Timeout/API down) keine stillen Freifahrten.
  • reason_header = "X-GPT-Reason": Kurzbegründung landet im Header (s.u. Datenschutz).
  • symbols_to_except: Offensichtliche interne Fälle werden neutralisiert, damit GPT nicht in klaren Situationen wirkt.
  • Limits: min_words = 1, max_size = 256k, timeout = 10s.

Metric/Scoring: drei GPT-Symbole

symbols {
  GPT_SPAM       { weight = 9.0;  group = "gpt"; description = "GPT: classified as SPAM"; }
  GPT_SUSPICIOUS { weight = 4.5;  group = "gpt"; description = "GPT: classified as SUSPICIOUS"; }
  GPT_HAM        { weight = -0.5; group = "gpt"; one_shot = true; description = "GPT: classified as HAM"; }
}

GPT wirkt wie ein starker, aber nicht absoluter Faktor.
SPAM (9.0): kräftiger Zuschlag.
SUSPICIOUS (4.5): sanfter Schubs Richtung Greylist/Review.
HAM (-0.5): kleine Entlastung, einmalig pro Mail.

Warum diese Gewichte?
Die Zahlen habe ich bewusst so gewählt, dass das GPT-Signal stark, aber nie absolut ist. Rspamd summiert Scores, GPT ist also nur ein Faktor:

  • GPT_SPAM = 9.0: genug, um bei Kombination mit klassischen Checks (Bayes, RBL, DMARC) die Add-Header-Schwelle sicher zu reißen, aber unterhalb von reject allein.
  • GPT_SUSPICIOUS = 4.5: halber Wert, schiebt Grauzonen in Richtung Greylist/Review, ohne sofortige Eskalation.
  • GPT_HAM = -0.5: nur eine kleine Entlastung (one_shot). So verhindert man, dass GPT-HAM mehrere Punkte abzieht und Spams „rettet“.

Wie wird die GPT-Gewichtung berechnet?
In den Logs/WebUI taucht das oft so auf: GPT_SPAM(2.10)[0.85]. Das bedeutet:

  • [0.85] = Rohwert von GPT, z. B. 85 % Wahrscheinlichkeit für Spam.
  • weight aus der Metric (z. B. 9.0 für GPT_SPAM).
  • Grundformel: Rohwert × weight → ergibt den Beitrag zum Gesamtscore.
  • Hinweis: Je nach Rspamd-Version kann der im Header gezeigte Wert zusätzlich skaliert sein (z. B. falls das Modell nur ein „softes“ Signal liefert). Deshalb sieht man in der Praxis häufig 2–8 Punkte statt des Maximalgewichts.

Actions/Schwellen

actions {
  greylist = 4;
  add_header = 6;
  reject = 15;
}

SUSPICIOUS (4.5) kippt oft in Greylist. SPAM (9.0) bringt fast immer Add-Header, Reject nur zusammen mit weiteren harten Befunden. Klassische Checks (SPF/DKIM/DMARC, RBL, Bayes) bleiben führend, GPT ergänzt nur.

Tuning
Zu bissig? Gewicht etwas senken.
Zu lasch? Gewicht erhöhen.
Zu optimistisch bei HAM? Gewicht kleiner machen oder 0 setzen.
Header mit X-GPT-Reason liefert Nachvollziehbarkeit, kann bei Bedarf wieder entfernt werden.

Praxis
– Symbole erscheinen im WebUI und Logfiles.
X-GPT-Reason erklärt im Header die Bewertung.
– Latenz/Kosten: gpt-4o-mini mit 160 Tokens und 10 s Timeout ist performant und günstig.

Jetzt schauen wir uns mal die Mailheader eines echten Beispiels an und wie GPT dort gegriffen hat:

X-Spamd-Result: default: False [8.59 / 15.00];
        VIOLATED_DIRECT_SPF(3.50)[];
        GPT_SPAM(2.10)[0.85];
        MISSING_MIMEOLE(2.00)[];
        CTYPE_MIXED_BOGUS(1.00)[];
        MID_RHS_NOT_FQDN(0.50)[];
        DMARC_POLICY_ALLOW_WITH_FAILURES(-0.50)[];
        MIME_HTML_ONLY(0.20)[];
        R_DKIM_ALLOW(-0.20)[thejewelbox.dd:s=1759374209.thejewelbox];
        ...

Erklärung:

  • X-Spamd-Result: [8.59 / 15.00] – Gesamtscore 8.59, Reject-Schwelle bei 15. Hier also kein Reject, sondern nur Add-Header.
  • GPT_SPAM(2.10)[0.85] – GPT meldet Spam mit 85 % Sicherheit ([0.85]). Daraus errechnet Rspamd den Beitrag ((…)), der in den Gesamtscore einfließt.
  • Die klassischen Checks wie VIOLATED_DIRECT_SPF(3.50) oder MISSING_MIMEOLE(2.00) haben ebenfalls beigetragen – GPT ist also nur ein Faktor im Gesamtbild.

Zusätzlich schreibt das GPT-Modul auf Wunsch auch eine kurze Begründung in den Mailheader:

X-GPT-Reason: This email is likely spam due to the urgency created around an unpaid invoice and the mismatch between the sender's domain and the company name.

Erklärung:

  • X-GPT-Reason – eigener Header, den du in gpt.conf mit reason_header = "X-GPT-Reason" aktivierst.
  • Der Text stammt direkt aus dem Modell und begründet die Einstufung (hier: Dringlichkeit „unpaid invoice“ + Domain/Company-Mismatch).
  • Nützlich für Analyse/Transparenz; kann auf MTA/MDA-Ebene wieder entfernt werden, wenn du ihn nicht bis zum Postfach durchreichen willst.

Ein Hinweis zum Datenschutz (gesamt)
Mit GPT-Integration gehen Mailinhalte an einen externen Dienst (z. B. OpenAI). Das kann datenschutzrechtlich relevant sein. Wer sensible oder personenbezogene Daten verarbeitet, sollte vorher prüfen, ob die Nutzung zulässig ist – oder alternativ ein selbst gehostetes, OpenAI-kompatibles Modell nutzen (z. B. Ollama). Den Reason-Header kannst du, falls nötig, serverseitig wieder entfernen.

Rspamd: Automatisches Spam/Ham-Lernen mit Dovecot und IMAPSieve

Bei jedem Spamfilter kann es vorkommen, dass Spam durchrutscht oder eine echte Nachricht fälschlicherweise als Spam eingestuft wird. Rspamd bietet über das Webinterface die Möglichkeit, trainiert zu werden. Hier kann man einfach den Quellcode jeder E-Mail kopieren und Rspamd mitteilen, ob es sich dabei um Spam (SPAM) oder um eine legitime Nachricht (HAM) handelt. Dieses Vorgehen ist jedoch ungeeignet, um den Spamfilter effektiv zu trainieren.

Dovecot and RSPAMD Logo

Wünschenswert wäre folgende Lösung: Immer wenn ein Benutzer eine E-Mail in den Ordner „Junk“ verschiebt, sollte diese E-Mail automatisch von Rspamd als Spam gelernt werden. Zusätzlich sollte jede E-Mail, die der Benutzer aus dem „Junk“-Ordner herausholt, als Ham gelernt werden.

Genau dieses Szenario möchte ich hier kurz beschreiben. Das Hostsystem ist ein FreeBSD; Linux-User müssen daher bei den Pfaden wie /usr/local aufpassen! Ebenfalls lauscht mein Rspamd-Worker nicht auf einem Unix-Socket, sondern auf der IP 127.0.0.3, da er in einer Jail-Umgebung läuft.

Beginnen wir mit der Konfiguration für Dovecot.

20-imap.conf:

protocol imap {
  mail_plugins = $mail_plugins sieve
}

90-plugin.conf:

plugin {
  sieve_plugins = sieve_imapsieve sieve_extprograms

  # From elsewhere to Spam folder or flag changed in Spam folder
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY FLAG
  imapsieve_mailbox1_before = file:/usr/local/etc/dovecot/sieve/report-spam.sieve

  # From Spam folder to elsewhere
  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/usr/local/etc/dovecot/sieve/report-ham.sieve

  sieve_pipe_bin_dir = /usr/local/libexec/dovecot

  sieve_global_extensions = +vnd.dovecot.pipe
}

/usr/local/etc/dovecot/sieve/report-spam.sieve:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "imap4flags"];

if environment :is "imap.cause" "COPY" {
    pipe :copy "sa-learn-spam.sh";
}

# Catch replied or forwarded spam
elsif anyof (allof (hasflag "\\Answered",
                    environment :contains "imap.changedflags" "\\Answered"),
             allof (hasflag "$Forwarded",
                    environment :contains "imap.changedflags" "$Forwarded")) {
    pipe :copy "sa-learn-spam.sh";
}

/usr/local/etc/dovecot/sieve/report-ham.sieve:

require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];

if environment :matches "imap.mailbox" "*" {
  set "mailbox" "${1}";
}

if string "${mailbox}" [ "Trash", "train_ham", "train_prob", "train_spam" ] {
  stop;
}

pipe :copy "sa-learn-ham.sh";

Natürlich nicht vergessen, die beiden neuen Sieve-Skripte für Sieve zu kompilieren:

# sievec /usr/local/etc/dovecot/sieve/report-spam.sieve
# sievec /usr/local/etc/dovecot/sieve/report-ham.sieve

Es fehlen nur noch die beiden Shell-Skripte, um die Mails an Rspamd weiterleiten zu können.

/usr/local/libexec/dovecot/sa-learn-spam.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_spam

/usr/local/libexec/dovecot/sa-learn-ham.sh:

#!/bin/sh
exec /usr/local/bin/rspamc -h 127.0.0.3:11334 learn_ham

Beide müssen ausführbar sein:

# chmod +x /usr/local/libexec/dovecot/sa-learn-spam.sh /usr/local/libexec/dovecot/sa-learn-ham.sh

Wenn ich nun eine E-Mail in den Ordner „Junk“ verschiebe, lernt Rspamd diese automatisch als Spam:

2020-05-04 11:21:02 #92071(controller) <b91225>; csession; rspamd_controller_check_password: allow unauthorized connection from a trusted IP 127.0.0.3
2020-05-04 11:21:02 #92071(controller) <b91225>; csession; rspamd_message_parse: loaded message; id: <FNgLHBeARhiVYgEegF_-Pw@ismtpd0002p1lon1.sendgrid.net>; queue-id: <undef>; size: 49053; checksum: <f5e2fc59515e1da33d532c6f03f6f6f0>
2020-05-04 11:21:02 #92071(controller) <b91225>; csession; rspamd_mime_part_detect_language: detected part language: de
2020-05-04 11:21:02 #92071(controller) <b91225>; csession; rspamd_mime_part_detect_language: detected part language: de
2020-05-04 11:21:02 #92071(controller) <b91225>; csession; rspamd_controller_learn_fin_task: <127.0.0.3> learned message as spam: FNgLHBeARhiVYgEegF_-Pw@ismtpd0002p1lon1.sendgrid.net

Verschiebe ich eine E-Mail aus dem Ordner „Junk“ heraus, wird sie, wie gewünscht, als Ham gelernt:

2020-05-04 11:20:51 #92071(controller) <a7fe42>; csession; rspamd_controller_check_password: allow unauthorized connection from a trusted IP 127.0.0.3
2020-05-04 11:20:51 #92071(controller) <a7fe42>; csession; rspamd_message_parse: loaded message; id: <FNgLHBeARhiVYgEegF_-Pw@ismtpd0002p1lon1.sendgrid.net>; queue-id: <undef>; size: 49053; checksum: <f5e2fc59515e1da33d532c6f03f6f6f0>
2020-05-04 11:20:51 #92071(controller) <a7fe42>; csession; rspamd_mime_part_detect_language: detected part language: de
2020-05-04 11:20:51 #92071(controller) <a7fe42>; csession; rspamd_mime_part_detect_language: detected part language: de
2020-05-04 11:20:51 #92071(controller) <a7fe42>; csession; rspamd_controller_learn_fin_task: <127.0.0.3> learned message as ham: FNgLHBeARhiVYgEegF_-Pw@ismtpd0002p1lon1.sendgrid.net

Fragen? Einfach fragen!

Ach, und was man nicht mehr verwenden sollte: das Antispam-Plugin – das ist „tot“.

© 2026 -=Kernel-Error=-RSS

Theme von Anders NorénHoch ↑