Files
nexarch/dms/docs/DOC-17-PRUEFPROTOKOLL.md
T
patrickandClaude Sonnet 5 43574f7872 DOC-17: malware-scan-vor-ablage
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>
2026-09-18 00:41:52 +02:00

5.6 KiB
Raw Blame History

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:

  • ClamdScanner spricht das clamd-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 zu thumbnail.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 ein malware_detected-Audit-Eintrag.
  • Scan-Status für Nutzer sichtbar: upload_sessions.scan_status über Service.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.