Files
AfiS-Aktensystem/docs/07-dateien-und-sicherheit.md
T
HarlekinandClaude Opus 5 4446e3f0ac Fachdokumente nach docs/, Entscheidungen festgehalten
CLAUDE.md und README.md verweisen auf docs/, die Dateien lagen aber im
Wurzelverzeichnis. Verschoben.

docs/13-entscheidungen.md hält fest, wo die Dokumente sich widersprechen und
wie es aufgelöst wurde. Der wichtigste Punkt: 04-datenmodell.md verlangt eine
Regel, die jedes UPDATE auf CaseEntryVersion verwirft, 05-akten-und-vorlagen.md
verlangt, dass eine Umklassifizierung rückwirkend auf alle Versionen wirkt.
Beides zusammen geht nicht. Aufgelöst über einen Trigger, der die Tabelle
anhängend hält, aber das Anheben von minClearance zulässt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 00:00:58 +02:00

3.5 KiB

Dateien und Sicherheit

Bedrohungsmodell, ehrlich

Geschlossene Gruppe, alle kennen sich. Das realistische Risiko ist nicht die geschmuggelte Ransomware, sondern eine hochgeladene SVG- oder HTML-Datei, die im Browser eines Kollegen Skriptcode ausführt und dessen Session übernimmt. Deshalb die Maßnahmen nach Wirkung sortiert und nicht nach Auffälligkeit.

Prüfkette

Upload → quarantine/<uuid>  →  Typprüfung  →  ClamAV  →  Neu kodieren  →  store/<uuid>
                                   │             │
                                   └─────────────┴──►  ScanStatus = BLOCKED

1. Annahme

  • Ziel ist quarantine/, Dateiname wird durch die Attachment-UUID ersetzt
  • Originalname nur in der Datenbank, beim Ausliefern wieder gesetzt
  • Limits: 25 MB pro Datei, 500 MB Kontingent pro Nutzer, 20 Uploads pro Minute
  • Der Datensatz wird sofort mit scanStatus = PENDING angelegt, damit die UI ihn zeigen kann

2. Typprüfung, Whitelist über Magic Bytes

Erlaubt: image/png, image/jpeg, image/webp, application/pdf, text/plain.

Geprüft wird mit file-type anhand des Inhalts, nie anhand der Endung. Stimmt der erkannte Typ nicht mit dem gemeldeten überein, ist die Datei blockiert.

Ausdrücklich verboten, auch wenn es bequem wäre: SVG (führt Skripte aus), HTML, ZIP und andere Archive, Office-Dokumente mit Makros, alles Ausführbare.

3. ClamAV

Eigener Container clamav/clamav:stable, angebunden über den clamd-Unix-Socket, geteiltes Volume mit quarantine/. freshclam läuft im Container. Timeout 30 Sekunden, bei Timeout FAILED und manuelle Freigabe durch einen Admin möglich.

4. Neu kodieren

  • Bilder: einmal durch sharp (.rotate().toFormat(...)). Entfernt EXIF, zerstört angehängte Nutzlast, erzeugt gleich das Vorschaubild (max. 480 px) und einen Blurhash für das Platzhalterbild.
  • PDF: qpdf --decrypt --remove-page-labels und Entfernen eingebetteter Dateien und JavaScript. Erste Seite wird als Vorschaubild gerendert.
  • Text: Zeichensatz nach UTF-8 normalisieren, Größe prüfen.

5. Freigabe

SHA-256 berechnen, in store/ verschieben, scanStatus = CLEAN, SSE-Ereignis attachment.scanned an alle offenen Ansichten der Akte.

Gleicher Hash bereits vorhanden: nur einen Verweis anlegen statt erneut zu speichern.

Auslieferung

Alles über GET /api/files/[id], niemals als statische Datei.

Content-Disposition: attachment; filename="protokoll_karlsen.pdf"
X-Content-Type-Options: nosniff
Cache-Control: private, max-age=31536000, immutable
Content-Security-Policy: default-src 'none'; sandbox

Der Endpunkt prüft vorher die Sichtbarkeit der zugehörigen Akte und protokolliert den Zugriff ab Stufe 3. Vorschaubilder laufen über denselben Endpunkt mit ?variant=thumb und dürfen inline ausgeliefert werden, weil sie serverseitig neu kodiert wurden.

Wenn möglich: eigene Subdomain files.afis.example.de ohne Session-Cookie, mit signierter, kurzlebiger URL. Dann kann selbst eine durchgerutschte Datei keine Session stehlen.

Weitere Härtung

  • Strikte CSP auf der App: script-src 'self', kein unsafe-inline (Next.js Nonce nutzen)
  • Rate Limits auf Login, Upload, Suche und Export
  • Alle Server Actions prüfen die Berechtigung erneut, egal was das UI vorher versteckt hat
  • Kein dangerouslySetInnerHTML für Nutzerinhalte. Markdown über remark mit rehype-sanitize, feste Allowlist.
  • Datenbank-Nutzer der App ohne SUPERUSER, Migrationen über einen zweiten Nutzer