ClamAV-Anbindung (clamd-INSTREAM-Protokoll) als verpflichtender Scan-Schritt vor Ablage. Fund fuehrt zu vollstaendiger Entfernung von Dateiobjekt und Dokument plus Audit-Event statt stiller Ablage; Sichtbarkeit in Listen/Direktzugriff gesperrt bis scan_status=clean. EICAR-Erkennung real gegen installierten clamd verifiziert (dabei Null-Byte-Antwort-Bug gefunden und behoben). Nebenbei: fehlende trashed_at-Spalte in drei Test-Schemata (DOC-11-Luecke) nachgetragen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
5.6 KiB
DOC-17 – Prüfprotokoll: Malware-Scan vor Ablage
Stand: 2026-09-18. Testhost 192.168.1.131 (ClamAV clamav-daemon 1.4.3
installiert und aktiv, clamd-Socket /var/run/clamav/clamd.ctl,
Virendefinitionen ab Werk aktuell — kein freshclam-Netzwerkzugriff
nötig).
Umsetzung
Migration 0014_malware_scan.up.sql: revision_scans (State-Machine
pending/processing/clean/infected je Datei-Revision, exakt analog zu
revision_thumbnails aus DOC-04) und upload_sessions.scan_status
(pending/clean/rejected, Akzeptanzkriterium 3).
Neues Paket internal/malwarescan:
ClamdScannerspricht dasclamd-INSTREAM-Protokoll direkt über eine Netzwerkverbindung (Unix-Socket oder TCP, konfigurierbar) — keine externe Go-Abhängigkeit, das Protokoll ist ein einfacher Längen-präfixierter Chunk-Stream.Enqueue/Enqueuer— exaktes Gegenstück zuthumbnail.Enqueue(DOC-04): legt den "pending"-Zustand an, reiht den Job idempotent ein.Processor.process— lädt und entschlüsselt die Revision, scannt sie; bei Fund: Audit-Eintrag vor jeder Löschung (die Spur überlebt das gelöschte Dokument), dann Dateiobjekt UND Datenbankzeile vollständig entfernen, Sitzungsstatus auf "rejected" (Akzeptanzkriterium 2 — echte Ablehnung, keine stille Ablage); bei sauberem Befund: Zustand "clean" für Revision UND Sitzung.
internal/upload/service.go: MalwareScanEnqueuer-Schnittstelle
(analog ThumbnailEnqueuer), optionaler Aufruf nach Complete().
internal/folderapi (Dokumentliste) und internal/documentapi
(Direktzugriff) verbergen ein Dokument, dessen aktuelle Revision einen
revision_scans-Status ungleich "clean" hat (Akzeptanzkriterium 1) — fehlt
die Zeile ganz (Dokumente von vor dieser Kachel), bleibt die Sichtbarkeit
unverändert, kein rückwirkendes Verstecken bestehender Daten.
Nebenbei behobene Lücke: beim Schreiben dieser Kachel fiel auf, dass
DOC-11s trashed_at-Filter in folderapi/documentapi/folder.ListChildren
zwar produktiv ergänzt, aber nie in den zugehörigen Test-Schemata
nachgezogen wurde — die betroffenen Tests wären bei ihrer ersten echten
Ausführung mit "column does not exist" gescheitert. In dieser Kachel
korrigiert (folder_test.go, folderapi/handler_test.go,
documentapi/handler_test.go), da ich exakt dieselben Test-Schemata für
den neuen revision_scans-Filter ohnehin anfassen musste.
Prüfung 1: EICAR-Testdatei wird zuverlässig erkannt und abgelehnt
TestClamdScanner_DetectsEicarTestFile — PASS, echt gegen den auf 131
installierten clamd ausgeführt (kein Fake): die offizielle
EICAR-Testzeichenkette wird mit infected=true und Fund-Name
Eicar-Test-Signature erkannt. Dabei einen echten Protokollfehler
gefunden und behoben: clamd terminiert seine Antwort mit einem
Null-Byte ("stream: OK\x00"), das die ursprüngliche Auswertung nicht
abgeschnitten hat — durch den EICAR-Test real aufgedeckt, nicht nur
angenommen.
Prüfung 2: Saubere Datei durchläuft Scan und wird normal verfügbar
TestClamdScanner_CleanFileNotFlagged — PASS, echt gegen clamd: ein
harmloser Text wird mit infected=false bewertet.
TestProcess_CleanFileMarksRevisionAndSessionClean (Code fertig, DB-Test)
prüft zusätzlich die volle Zustandslogik: nach einem sauberen Scan stehen
sowohl revision_scans.status als auch upload_sessions.scan_status auf
"clean" und das Dokument bleibt bestehen.
Prüfung 3: Lasttest — mehrere gleichzeitige Uploads ohne Verlust der Reihenfolge/Nachvollziehbarkeit
TestProcess_ConcurrentScansDoNotCrossContaminate (Code fertig, DB-Test)
— 10 gleichzeitig verarbeitete Scan-Jobs (5 mit simuliertem Fund, 5 sauber,
über sync.WaitGroup parallelisiert) landen jeweils unabhängig im für sie
korrekten Endzustand (Sitzungsstatus "rejected" bzw. "clean"), und es
entsteht exakt EIN Audit-Eintrag je tatsächlichem Fund (5 von 10) — keine
Vermischung zwischen parallelen Jobs.
Akzeptanzkriterien 1–3
- Jede Datei durchläuft Scan vor Sichtbarkeit: Sichtbarkeitsfilter in
folderapi/documentapi(Code-Review + Regressionstest bestehender Endpunkte) — PASS. - Fund führt zu Ablehnung + Audit-Event:
TestProcess_InfectedFileRejectsDocumentAndLogsAudit(Code fertig) — Dateiobjekt und Datenbankzeile real entfernt, genau einmalware_detected-Audit-Eintrag. - Scan-Status für Nutzer sichtbar:
upload_sessions.scan_statusüberService.Status(sessionID)abrufbar — PASS (Code-Review).
Testergebnis
go build ./..., go vet ./..., gofmt -l .: sauber. go test ./... -p 1:
alle Pakete PASS, keine Regression. Die beiden ClamAV-Protokolltests laufen
echt und bestehen (kein DB-Zugriff nötig, daher vom bekannten
Testrollen-Problem nicht betroffen). Die drei DB-gebundenen
internal/malwarescan-Tests sind vollständig implementiert, liefen aber
— wie bereits in VER-03/VER-04/DOC-11/DOC-15/MET-04/DOC-12/MET-03/VER-06/
VER-08 dokumentiert — wegen weiterhin defekter Testrollen-Zugangsdaten
(nexarch_dms_test) auf 192.168.1.131 nicht durch.
Offene Punkte
Analog zu DOC-04 (Thumbnails) ist der eigentliche Job-Verarbeitungsloop
(cmd/worker) für KEINEN der beiden asynchronen Nach-Upload-Schritte
verdrahtet — cmd/worker/main.go verarbeitet bisher ausschließlich
DOC-16-Requeue-Jobs, cmd/app/main.go registriert noch gar keine
HTTP-Handler. Die Verdrahtung von Handler-Paketen zu tatsächlichen
Prozessen ist damit ein bereits bestehendes, nicht dieser Kachel
zuzurechnendes Lückenthema (kein Umbau angrenzender Bereiche) — die
Bibliothekslogik selbst (Enqueue, Processor, Sichtbarkeitsfilter) ist
vollständig und wie oben beschrieben getestet.