7.6 KiB
Moderation Bot & Content Scanning
Status: ✅ Draupnir deployed (2026-07-29, Closes Issue #18) | ✅ Content Scanner deployed + live getestet (2026-07-29, Closes Issue #19)
Konfiguration: apps/production/draupnir*.yaml, apps/production/clamav*.yaml, apps/production/clamav_spam_checker.py
1. Draupnir (Moderationsbot)
Community-Nachfolger von Mjolnir. Läuft als eigener Bot-Account (@draupnir:axion1337.chat),
verwaltet Ban-Listen ("Policy Rooms") und setzt sie in geschützten Räumen durch.
Warum Draupnir statt Mjolnir?
Mjolnir gilt als Vorgänger-Projekt und wird von der Community nicht mehr aktiv weiterentwickelt; Draupnir ist der aktive Fork mit denselben Kernfunktionen plus Erweiterungen (u.a. native Rust-Crypto-Unterstützung, siehe unten).
Bot-Account & Zugriff (Bootstrap)
Da Authentifizierung über MAS läuft (kein klassisches registration_shared_secret), wird der
Bot-Account über MAS' eigenes CLI-Tool angelegt:
kubectl exec -it -n matrix deploy/matrix-stack-matrix-authentication-service -- \
mas-cli manage register-user draupnir --yes
kubectl exec -it -n matrix deploy/matrix-stack-matrix-authentication-service -- \
mas-cli manage issue-compatibility-token draupnir
Der ausgegebene Token wird per sops apps/production/draupnir-secret.yaml manuell eingetragen
(kein automatisierter Schritt - der Token darf nirgends unverschlüsselt landen).
Wichtige Stolpersteine (live gefunden, nicht aus der Doku ableitbar)
- Version:
gnuxie/draupnir:v2.9.0crasht beim ersten Start mitinitialManager("Can't join remote room because no servers..."). Das automatische Anlegen des Management-Rooms überinitialManagerfunktioniert erst ab v3.1.0. Aktuell deployt:v3.1.0. - CLI-Argument statt Env-Var: v3.x hat die automatische Config-Erkennung über
NODE_CONFIG_DIRentfernt - der Container braucht jetzt explizitargs: ["bot", "--draupnir-config", "/data/config/default.yaml"], sonstTypeError: No configuration path has been found for Draupnir.(per Extraktion vondist/config.jsaus dem Image bestätigt, nicht dokumentiert gefunden). - NetworkPolicy: Der Bot muss Synapse direkt anrufen können. Da
matrix-stack-synapseintern über haproxy geroutet wird undallow-ingress-haproxystandardmäßig nur Traefik (kube-system) erlaubt, braucht Draupnir eine eigenepodSelector-Ausnahme innetworkpolicy.yaml- sonst schlägt jede Anfrage an den Homeserver silent fehl.
Verschlüsselter Management-Room
Standardmäßig unverschlüsselt (Draupnirs zugrundeliegende Bot-Library aktiviert Crypto nicht automatisch). Für einen verschlüsselten Management-Room:
experimentalRustCrypto: truein der Config ergänzen (viasops) - vom Hersteller selbst als "not considered production safe" gekennzeichnet, in unserem Test aber ohne Fehler gelaufen (Pod stabil, kein Crash,End-to-end encryption enabledin den Logs).- Verschlüsselung ist eine Raum-Eigenschaft, die beim Erstellen gesetzt wird - das Flag allein verschlüsselt einen bereits bestehenden Management-Room nicht rückwirkend. Dafür in Element: Raumeinstellungen → Sicherheit & Datenschutz → Verschlüsselung aktivieren.
Profilbild setzen
Erfordert eine mxc://-URL (Bild muss zuerst hochgeladen werden, z.B. per Chat an den Bot
senden, dann in Element per "View Source" die mxc://-URL kopieren):
!draupnir avatar mxc://<server>/<media-id>
Befehle (Kurzreferenz)
Alle Befehle im (verschlüsselten) Management-Room, Präfix !draupnir:
| Befehl | Zweck |
|---|---|
status |
Bot-Status, beobachtete Listen, geschützte Räume |
rooms add <room> |
Raum unter Draupnirs Schutz stellen (Voraussetzung für Bans!) |
list create <shortcode> <alias> |
Neue Policy-Liste anlegen (wird automatisch beobachtet + geschützt) |
watch <shortcode> |
Zusätzliche Policy-Liste beobachten |
ban <user> <liste> <grund> |
Wichtig: 2. Argument ist die Policy-Liste, NICHT der Ziel-Raum! Der Ban gilt automatisch in allen Räumen, die diese Liste beobachten und geschützt sind |
kick <user> <room> <grund> |
Direkter, sofortiger Kick aus einem konkreten Raum (ohne Listen-Umweg) |
rules |
Zeigt die Regeln einer Policy-Liste an |
unban <user> <liste> |
Regel wieder entfernen |
Live getestet (2026-07-29): Testraum geschützt, Policy-Liste angelegt, Testnutzer über
ban+Liste erfolgreich aus dem geschützten Raum entfernt. Kernmechanismus bestätigt
funktionsfähig.
2. Content Scanner (Issue #19)
Verworfener erster Ansatz: matrix-content-scanner-python ist ein Proxy, den der
Client explizit statt der normalen Media-Endpunkte aufrufen muss - Synapse selbst leitet
nichts automatisch dorthin um. Diese client-seitige Unterstützung existiert nur noch in
veralteten, nicht mehr gepflegten Android/iOS-SDKs; weder aktuelles Element Web noch Element X
unterstützen das (geprüft: kein content_scanner-Hook im offenen element-x-android-Repo).
Element selbst hat echtes serverseitiges Scanning - aber nur in der kommerziellen
Element Pro + ESS Pro-Kombination, nicht in unserer offenen ESS-Community-Installation.
Tatsächlich umgesetzt: ein eigenes, kleines Synapse-Modul (clamav_spam_checker.py),
das Synapses echten, dokumentierten Hook check_media_file_for_spam nutzt - läuft
serverseitig, transparent für jeden Client, ganz ohne Mitwirkung des Clients. Kein
fertiges Modul dafür existiert (auch das verbreitete synapse-http-antispam-Brückenmodul
schließt genau diesen Callback explizit aus), daher selbst geschrieben.
Architektur:
- ClamAV (
clamav/clamav:1.5.3) läuft als eigener Pod, PVC für die Signatur-Datenbank. - Das Modul (
apps/production/clamav_spam_checker.py) wird per ConfigMap gemounted und überPYTHONPATHimportierbar gemacht (synapse.extraVolumes/extraVolumeMounts/extraEnv- kein Custom-Synapse-Image nötig). - Spricht ClamAVs natives INSTREAM-Protokoll direkt über Twisted-Netzwerk-Primitives
(
HostnameEndpoint/connectProtocol), nicht überasyncio- Synapse läuft auf Twisteds Reactor, nicht auf einer laufenden asyncio-Event-Loop. Ein erster Versuch mitasyncio.open_connection/wait_forschlug live mitRuntimeError: no running event loopfehl und fiel dadurch (durch das eigene Fail-Open-Verhalten) unbemerkt auf "durchlassen" zurück - die EICAR-Testdatei wurde beim ersten Versuch nicht erkannt. Nach Umstellung auf Twisted-Primitives funktioniert es sauber. - Fail-open bei Scanner-Fehlern (Verbindungsfehler/Timeout → Datei wird durchgelassen, laut geloggt) - ein ClamAV-Ausfall soll nicht alle Uploads auf dem Homeserver blockieren.
Live getestet und bestätigt (2026-07-29):
- Normale Datei in unverschlüsseltem Raum → läuft durch (kein Regressionsschaden).
- EICAR-Testdatei in unverschlüsseltem Raum → zuverlässig blockiert
(
ClamAV rejected an upload: Eicar-Test-Signature, Client bekommt400 Bad content- Synapse gibt bewusst keine Begründung an den Client zurück, nur in den Server-Logs sichtbar). - EICAR-Testdatei in verschlüsseltem Raum/DM → läuft durch - erwartete, strukturelle Grenze: Synapse hat bei E2EE nie den Entschlüsselungsschlüssel, sieht nur Ciphertext. Nur ein kooperierender Client könnte das lösen (siehe oben, existiert nicht offen verfügbar).
Bekannte Deckungslücke: schützt nur unverschlüsselte Räume/DMs (viele öffentliche/ föderierte Räume) - keine Warnung/Kennzeichnung für Nutzer in verschlüsselten Räumen, dass dort kein Scanning stattfindet. Folgeidee (Issue #43, LOW): Grafana-Dashboard über die bestehenden Loki-Logs, um Erkennungen/Scanner-Ausfälle sichtbar zu machen.