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>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user