[LOW] Content scanner for uploaded media #19

Closed
opened 2026-07-28 14:31:49 +00:00 by sorb · 3 comments
Owner

matrix-content-scanner + ClamAV antivirus scanning for uploaded media, blocking suspicious files. Optional but good practice given federation is open.

matrix-content-scanner + ClamAV antivirus scanning for uploaded media, blocking suspicious files. Optional but good practice given federation is open.
sorb added the area:securitypriority:low labels 2026-07-28 14:31:49 +00:00
Author
Owner

Update 2026-07-29 — Real umgesetzt und live getestet, Issue geschlossen

Der ursprünglich geplante matrix-content-scanner + ClamAV-Ansatz erwies sich als Sackgasse:
das ist ein Proxy, den der Client explizit statt der normalen Media-Endpunkte aufrufen
muss - weder aktuelles Element Web noch Element X unterstützen das (geprüft: kein
Content-Scanner-Hook im offenen element-x-android-Repo). Element hat echtes serverseitiges
Scanning nur in der kommerziellen Element Pro + ESS Pro-Kombination, nicht in unserer offenen
ESS-Community-Installation.

Stattdessen umgesetzt: ein eigenes, kleines Synapse-Modul
(apps/production/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 Client-Mitwirkung. Kein fertiges Modul dafür existiert (auch synapse-http-antispam
schließt genau diesen Callback aus), daher selbst geschrieben.

Architektur: ClamAV als eigener Pod + PVC. Modul per ConfigMap gemounted, über
PYTHONPATH importierbar (synapse.extraVolumes/extraVolumeMounts/extraEnv) - kein
Custom-Synapse-Image nötig. Fail-open bei Scanner-Fehlern, damit ein ClamAV-Ausfall nicht alle
Uploads blockiert.

Ein echter Bug unterwegs gefunden und gefixt: erster Versuch nutzte asyncio.open_connection/
wait_for für die Verbindung zu clamd - schlug live mit RuntimeError: no running event loop
fehl, da Synapse auf Twisteds Reactor läuft, nicht auf einer laufenden asyncio-Event-Loop. Durch
das eigene Fail-Open-Verhalten blieb das zunächst unbemerkt (die EICAR-Testdatei wurde beim
ersten Versuch nicht erkannt, lief einfach durch). Nach Umstellung auf Twisteds eigene
Netzwerk-Primitives (HostnameEndpoint/connectProtocol) funktioniert es sauber.

Live-Tests bestätigt (2026-07-29):

  • Normale Datei in unverschlüsseltem Raum → durchgelassen (kein Regressionsschaden)
  • EICAR-Testdatei in unverschlüsseltem Raum → zuverlässig blockiert
    (ClamAV rejected an upload: Eicar-Test-Signature, Client erhält 400 Bad content)
  • EICAR-Testdatei in verschlüsseltem Raum/DM → läuft durch (erwartete, strukturelle Grenze -
    Synapse hat bei E2EE nie den Entschlüsselungsschlüssel, nur Ciphertext sichtbar)

Bekannte Deckungslücke: schützt nur unverschlüsselte Räume/DMs. Für E2EE bräuchte es einen
kooperierenden Client (existiert aktuell nicht offen verfügbar) - das ist eine strukturelle
Grenze des Matrix-Protokolls selbst, kein Implementierungsdetail.

Details: docs/deployment-guides/06-moderation-content-scanning.md, Wiki
Moderation-Content-Scanning. Folgeidee (Grafana-Dashboard über bestehende Loki-Logs für
Erkennungen/Ausfälle): Issue #43.

**Update 2026-07-29 — Real umgesetzt und live getestet, Issue geschlossen** Der ursprünglich geplante `matrix-content-scanner` + ClamAV-Ansatz erwies sich als Sackgasse: das ist ein Proxy, den der **Client** explizit statt der normalen Media-Endpunkte aufrufen muss - weder aktuelles Element Web noch Element X unterstützen das (geprüft: kein Content-Scanner-Hook im offenen `element-x-android`-Repo). Element hat echtes serverseitiges Scanning nur in der kommerziellen Element Pro + ESS Pro-Kombination, nicht in unserer offenen ESS-Community-Installation. **Stattdessen umgesetzt**: ein eigenes, kleines Synapse-Modul (`apps/production/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 Client-Mitwirkung. Kein fertiges Modul dafür existiert (auch `synapse-http-antispam` schließt genau diesen Callback aus), daher selbst geschrieben. **Architektur**: ClamAV als eigener Pod + PVC. Modul per ConfigMap gemounted, über `PYTHONPATH` importierbar (`synapse.extraVolumes`/`extraVolumeMounts`/`extraEnv`) - kein Custom-Synapse-Image nötig. Fail-open bei Scanner-Fehlern, damit ein ClamAV-Ausfall nicht alle Uploads blockiert. **Ein echter Bug unterwegs gefunden und gefixt**: erster Versuch nutzte `asyncio.open_connection`/ `wait_for` für die Verbindung zu clamd - schlug live mit `RuntimeError: no running event loop` fehl, da Synapse auf Twisteds Reactor läuft, nicht auf einer laufenden asyncio-Event-Loop. Durch das eigene Fail-Open-Verhalten blieb das zunächst unbemerkt (die EICAR-Testdatei wurde beim ersten Versuch nicht erkannt, lief einfach durch). Nach Umstellung auf Twisteds eigene Netzwerk-Primitives (`HostnameEndpoint`/`connectProtocol`) funktioniert es sauber. **Live-Tests bestätigt** (2026-07-29): - Normale Datei in unverschlüsseltem Raum → durchgelassen (kein Regressionsschaden) - EICAR-Testdatei in unverschlüsseltem Raum → zuverlässig blockiert (`ClamAV rejected an upload: Eicar-Test-Signature`, Client erhält `400 Bad content`) - EICAR-Testdatei in verschlüsseltem Raum/DM → läuft durch (erwartete, strukturelle Grenze - Synapse hat bei E2EE nie den Entschlüsselungsschlüssel, nur Ciphertext sichtbar) **Bekannte Deckungslücke**: schützt nur unverschlüsselte Räume/DMs. Für E2EE bräuchte es einen kooperierenden Client (existiert aktuell nicht offen verfügbar) - das ist eine strukturelle Grenze des Matrix-Protokolls selbst, kein Implementierungsdetail. Details: `docs/deployment-guides/06-moderation-content-scanning.md`, Wiki [[Moderation-Content-Scanning]]. Folgeidee (Grafana-Dashboard über bestehende Loki-Logs für Erkennungen/Ausfälle): Issue #43.
sorb closed this issue 2026-07-29 13:57:54 +00:00
Author
Owner

Update 2026-07-29 (später) — Client-seitige Erweiterung für verschlüsselte Räume

Nach der ersten Umsetzung (Synapse-Modul, siehe Kommentar oben) kam die Nachfrage, ob auch
E2EE-Räume abgedeckt werden können, da der Nutzer bereits ThreadNet-Web forkt. Umgesetzt:

Neuer Dienst: apps/production/clamav-http-scanner.py - macht den bestehenden
ClamAV-Pod für Browser-JS erreichbar (https://axion1337.chat/_scan), da clamd nur rohes
TCP spricht. Auth via Synapses eigenem /whoami-Endpunkt, fail-open bei Scanner-Fehlern
(gleiche Philosophie wie das Synapse-Modul).

Zwei Patch-Stellen im ThreadNet-Web-Fork (nicht in diesem Repo):

  • Empfang: apps/web/src/utils/DecryptFile.ts (decryptFile()) - einziger Punkt für alle
    Anhangstypen.
  • Versand: apps/web/src/ContentMessages.ts (uploadFile()) - eine Funktion für alle
    Uploads (Datei, Thumbnails, Sprachnachrichten), unabhängig von Raumverschlüsselung.

Live getestet:

  • EICAR in verschlüsseltem Gruppenraum + 1:1-DMs → vor Upload blockiert (vorher lief das
    durch, da Synapse E2EE-Inhalte nie sieht).
  • Normale Datei verschlüsselt/unverschlüsselt → durchgelassen (kein Regressionsschaden).
  • Empfangsseite unabhängig vom Absender: EICAR über einen echten, ungepatchten Client
    (app.element.io) in denselben Raum geschickt (simuliert föderierten/fremden Absender) -
    beim Download im gepatchten Client greift der Scanner trotzdem zuverlässig.

Korrigierte Annahme zu Electron/Desktop: ursprünglich angenommen, der Fix gelte
automatisch nicht für Electron. Tatsächlich hat apps/desktop einen
webapp-artifact-Mechanismus, der den eigenen Fork-Build übernehmen würde - aber kein
laufender CI-Runner, und der Build-Job checkt noch das Upstream-Repo aus. Die bestehende
Desktop-Build (mit der Discord-Style-Raumliste) stammt aus einem manuellen, nicht
wiederholten Build-Vorgang. Neues Issue dafür:
#44. Element X (Mobile)
komplett unberührt (eigene Codebasis, matrix-rust-sdk).

Deckung jetzt: Web-Client (bestätigt, beide Richtungen, verschlüsselt + unverschlüsselt) +
Synapse-Modul (alle Clients, nur unverschlüsselt). Electron/Desktop und Element X bleiben
offene Lücken, erstere mit konkretem Fahrplan in #44.

Details: docs/deployment-guides/06-moderation-content-scanning.md, Wiki
Moderation-Content-Scanning.

**Update 2026-07-29 (später) — Client-seitige Erweiterung für verschlüsselte Räume** Nach der ersten Umsetzung (Synapse-Modul, siehe Kommentar oben) kam die Nachfrage, ob auch E2EE-Räume abgedeckt werden können, da der Nutzer bereits `ThreadNet-Web` forkt. Umgesetzt: **Neuer Dienst**: `apps/production/clamav-http-scanner.py` - macht den bestehenden ClamAV-Pod für Browser-JS erreichbar (`https://axion1337.chat/_scan`), da clamd nur rohes TCP spricht. Auth via Synapses eigenem `/whoami`-Endpunkt, fail-open bei Scanner-Fehlern (gleiche Philosophie wie das Synapse-Modul). **Zwei Patch-Stellen im `ThreadNet-Web`-Fork** (nicht in diesem Repo): - Empfang: `apps/web/src/utils/DecryptFile.ts` (`decryptFile()`) - einziger Punkt für alle Anhangstypen. - Versand: `apps/web/src/ContentMessages.ts` (`uploadFile()`) - eine Funktion für alle Uploads (Datei, Thumbnails, Sprachnachrichten), unabhängig von Raumverschlüsselung. **Live getestet**: - EICAR in verschlüsseltem Gruppenraum + 1:1-DMs → vor Upload blockiert (vorher lief das durch, da Synapse E2EE-Inhalte nie sieht). - Normale Datei verschlüsselt/unverschlüsselt → durchgelassen (kein Regressionsschaden). - **Empfangsseite unabhängig vom Absender**: EICAR über einen echten, ungepatchten Client (app.element.io) in denselben Raum geschickt (simuliert föderierten/fremden Absender) - beim Download im gepatchten Client greift der Scanner trotzdem zuverlässig. **Korrigierte Annahme zu Electron/Desktop**: ursprünglich angenommen, der Fix gelte automatisch nicht für Electron. Tatsächlich hat `apps/desktop` einen `webapp-artifact`-Mechanismus, der den eigenen Fork-Build übernehmen würde - aber kein laufender CI-Runner, und der Build-Job checkt noch das Upstream-Repo aus. Die bestehende Desktop-Build (mit der Discord-Style-Raumliste) stammt aus einem manuellen, nicht wiederholten Build-Vorgang. Neues Issue dafür: [#44](https://rohana.axion1337.de/sorb/axion1337.chat-gitops/issues/44). Element X (Mobile) komplett unberührt (eigene Codebasis, matrix-rust-sdk). Deckung jetzt: Web-Client (bestätigt, beide Richtungen, verschlüsselt + unverschlüsselt) + Synapse-Modul (alle Clients, nur unverschlüsselt). Electron/Desktop und Element X bleiben offene Lücken, erstere mit konkretem Fahrplan in #44. Details: `docs/deployment-guides/06-moderation-content-scanning.md`, Wiki [[Moderation-Content-Scanning]].
Author
Owner

Migriert nach git.lab: axion1337.chat/axion1337.chat-gitops#19 (nur im Lab bzw. via VPN erreichbar — das Lab ist seit 2026-08-01 die Quelle der Wahrheit, siehe gitops#48). Weiterarbeit dort; dieses Gitea-Issue bleibt als Verweis stehen.

**Migriert nach git.lab**: [axion1337.chat/axion1337.chat-gitops#19](https://git.lab/axion1337.chat/axion1337.chat-gitops/-/issues/19) (nur im Lab bzw. via VPN erreichbar — das Lab ist seit 2026-08-01 die Quelle der Wahrheit, siehe gitops#48). Weiterarbeit dort; dieses Gitea-Issue bleibt als Verweis stehen.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sorb/axion1337.chat-gitops#19