All checks were successful
docs/vs-nfd/90-restrisiken.md: nine entries, each with risk, why it is accepted, compensating control and decider — unmarked attachment content (#212), local auth not yet switchable incl. the open runtime- flippability question (#216), deferred plugin hash pinning (#232), the one-time git-history secret check with its pattern caveat (#198), digest-mail titles (I-23, revisit M32), page_links slug residue (I-24), the IndexedDB endpoint copy (I-25), deliberately unscheduled features, and the Site-Admin read bypass. Binding same-PR maintenance rule stated; referenced from the delimitation statement and consumed by the Grundschutz mapping. Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
283 lines
13 KiB
Markdown
283 lines
13 KiB
Markdown
# Maßnahmenplan VS-NfD — Dorfteich (Rev. 2, volle Tiefe)
|
||
|
||
Ziel: **einsetzbar in einer nach VSA freigegebenen Umgebung**, keine eigene
|
||
BSI-Zulassung. Diese Revision zieht die vormals als Roadmap geführten Punkte
|
||
in die Planung und weist die Terminwirkung aus.
|
||
|
||
Aufwand in **Arbeitstagen (AT)** für eine Person mit Claude Code.
|
||
Erfahrungswert: Implementierung ist der kleinere Teil, Test und Dokumentation
|
||
der größere. Die Schätzungen enthalten beides.
|
||
|
||
---
|
||
|
||
**Stand 2026-07-30:** In Meilensteine `M24`–`M31` und Issues #188–#236
|
||
überführt (Volltexte und Anlage-Protokoll: `31-issue-entwurf.md`,
|
||
Befundgrundlage: `10-ist-aufnahme.md`). Issue-Nummern stehen an den
|
||
Checkboxen, Meilensteine an den Phasen.
|
||
|
||
## Phase 0 — Nicht bauen (Leitplanken)
|
||
|
||
Diese Dinge machen die Situation **schlechter**, weil sie die Anwendung zur
|
||
Trägerin einer Sicherheitsgrundfunktion nach §52 VSA machen würden:
|
||
|
||
- ❌ Keine eigene Backup-Verschlüsselung — Datenträgerschutz ist Plattformsache
|
||
- ❌ Keine Verschlüsselung von Inhalten in DB oder Filesystem
|
||
- ❌ Kein eigenes MFA/TOTP, keine eigene Passwort-Policy-Engine
|
||
- ❌ Keine neuen Krypto-Primitive
|
||
- ❌ Keine anwendungsseitige Trennung von Einstufungsniveaus
|
||
|
||
Stattdessen: **delegieren und dokumentieren.**
|
||
|
||
---
|
||
|
||
## Phase 1 — Blocker · Summe 30–38 AT
|
||
|
||
### P1-1 Fremdauthentisierung + lokale Auth abschaltbar · 10–12 AT
|
||
|
||
_Meilenstein: `M27 — VS-NfD: external authentication`_
|
||
|
||
- [ ] OIDC Authorization Code + PKCE gegen `UserIdentity.provider` (ADR 0007
|
||
ausbauen), Keycloak als Referenz-IdP · 5–6 AT · #214
|
||
- [ ] Alternativpfad vertrauenswürdiger Reverse-Proxy-Header bzw. mTLS-
|
||
Client-Zertifikat · 2 AT · #215
|
||
- [ ] **Harter Schalter `auth.local.enabled = false`** inkl. Reset- und
|
||
Registrierungs-Flows, PATs und Feed-Tokens · 2 AT · #216
|
||
- [ ] Gruppen-/Rollen-Mapping aus IdP-Claims auf das Permission-Modell · 2–3 AT · #217
|
||
|
||
### P1-2 Einstufung als First-Class-Metadatum · 14–18 AT
|
||
|
||
_Meilenstein: `M26 — VS-NfD: classification metadata`_
|
||
|
||
- [x] Enum-Feld `classification` an `Page`, Migration, Default aus
|
||
Instance-Setting · 2 AT · #204
|
||
- [x] Vererbung im Seitenbaum, Herabstufung nur mit eigenem Recht + Audit · 3 AT · #205
|
||
- [ ] Durchreichen in alle Ausgabekanäle · 8–12 AT · #206–#212
|
||
- [x] Web-Ansicht (Kopf/Fuß) · 1 AT · #206
|
||
- [x] **Print-CSS** (`@media print`, Kopf/Fuß je Seite) — fehlt komplett · 1 AT · #207
|
||
- [x] PDF via gotenberg (`pdf-html.ts` Header/Footer-Template) · 1 AT · #208
|
||
- [x] DOCX/ODT via pandoc (Reference-Doc mit Kopf-/Fußzeile) · 2–3 AT · #209
|
||
- [x] Markdown-ZIP (Frontmatter + Aufdruck) · 1 AT · #210
|
||
- [ ] Atom-Feeds, Public-API, Suchergebnisse, No-JS-Shell · 2–3 AT · #211
|
||
- [ ] Attachment-Download (Dateiname-Präfix + Begleitdatei) · 1–2 AT · #212
|
||
- [ ] Warnung/Sperre beim Anhängen an eingestufte Seiten · 1 AT · #213
|
||
|
||
### P1-3 Verifizierter Offline-/Airgap-Pfad · 8–10 AT ⟵ neu aus Roadmap
|
||
|
||
_Meilenstein: `M28 — VS-NfD: offline/airgap deployment` — das
|
||
Digest-Pinning (#203) läuft vorgezogen in `M25`_
|
||
|
||
Hochgezogen, weil das eine Frage im **ersten** Behördengespräch ist. „Sollte
|
||
gehen" ist dort eine schlechtere Antwort als „getestet, hier ist die Anleitung".
|
||
|
||
- [x] Alle Images auf Digest pinnen (schließt den `gotenberg:8`-Punkt ein) · 1 AT · #203
|
||
- [ ] Mirror-Verfahren in interne Registry dokumentieren · 1 AT · #218
|
||
- [ ] Build ohne Netz reproduzierbar (pnpm Offline-Store / reine
|
||
Prebuilt-Images) · 2–3 AT · #219
|
||
- [ ] Testlauf in netzisolierter Umgebung, Protokoll als Beleg · 2 AT · #220
|
||
- [ ] Offline-Update-Pfad inkl. Migrationen · 2–3 AT · #221
|
||
|
||
---
|
||
|
||
## Phase 2 — Billig, hohe Prüfer-Signalwirkung · Summe 22–28 AT
|
||
|
||
_Meilensteine: `M24 — VS-NfD: security quick wins`; die nachgezogenen
|
||
Punkte (#199, #200, #201, #202) in `M25 — VS-NfD: hardening & supply
|
||
chain`_
|
||
|
||
- [x] **Schlüsseltrennung `COLLAB_TOKEN_SECRET`** per HKDF (zweckgebundene
|
||
Subkeys) — echter Fund, vor allen Features · 1–2 AT · #188
|
||
- [x] **Eigenbau-HMAC-JWT durch `jose` ersetzen** · +2–3 AT · #188 ⟵ neu aus Roadmap
|
||
_Gebündelt mit der Zeile darüber, weil dieselbe Datei
|
||
(`packages/shared/src/token-crypto.ts`). Einzeln wären es 5–6 AT._
|
||
Achtung: Unsubscribe-Tokens leben lang in versandten Mails →
|
||
Dual-Verify-Fenster einplanen.
|
||
- [x] **CSRF fail-closed** — fehlendes Origin _und_ Referer wird derzeit
|
||
durchgelassen · 1 AT · #189
|
||
- [x] **Session-Timeout konfigurierbar**, Default deutlich unter 30 Tagen,
|
||
separates Idle-Timeout · 1–2 AT · #190
|
||
- [x] **Feed-Token raus aus dem Query-Parameter**, alternativ Feeds hart
|
||
abschaltbar · 2 AT · #191
|
||
- [x] **Backup-Ziele einschränkbar** — Allowlist, WebDAV/rsync per Deploy
|
||
vollständig deaktivierbar · 2 AT · #192
|
||
- [x] **Pond-Purge implementieren** — getrashte Ponds bleiben ewig liegen · 3 AT · #193
|
||
- [x] **Orphan-File-Sweep** implementieren, `Attachment.deletedAt` nutzen
|
||
oder entfernen · 2 AT · #194
|
||
- [x] **Papierkorb aus dem Suchindex** entfernen statt query-seitig filtern · 2 AT · #195
|
||
- [x] **Retention-Job für `audit_log`** · 1 AT · #196
|
||
- [x] **Security-Header** (helmet), CORS explizit restriktiv · 1 AT · #197
|
||
- [x] **SBOM in CI** (CycloneDX/syft) + Lizenzreport als Artefakt · 1–2 AT · #202
|
||
- [x] `deploy/compose/.env` prüfen, Beispieldatei statt Realdatei · 0,5 AT · #198
|
||
- [x] **Attachment-Integritätshashes** · +2–3 AT · #199 ⟵ neu aus Roadmap
|
||
SHA-256-Spalte, Berechnung beim Upload, Prüfung beim Download,
|
||
Backfill-Migration. Nebennutzen: Orphan-Sweep, Dedup, Backup-Verifikation.
|
||
- [x] **Plugins hart abschaltbar** (`plugins.enabled = false`) · +2 AT · #200 ⟵ neu
|
||
Deckt das Risiko „Codeausführung in der VS-Zone" für den
|
||
Angebotsstand vollständig ab. Hash-Pinning siehe Phase 4.
|
||
- [x] **Syslog/SIEM: Ereigniskatalog** · +3–4 AT · #201 ⟵ neu aus Roadmap
|
||
Der Code-Anteil ist klein (stdout-JSON reicht meist). Wert liegt im
|
||
**stabilen Ereigniskatalog**: feste Event-IDs, dokumentierte Semantik
|
||
und Felder, damit die Behörde SIEM-Regeln schreiben kann.
|
||
|
||
---
|
||
|
||
## Phase 3 — Beweissicherung / Lesezugriffe · 8–20 AT ⟵ neu aus Roadmap
|
||
|
||
_Meilenstein: `M29 — VS-NfD: read-access audit trail` (Variante A)_
|
||
|
||
Der aufwändigste der nachgezogenen Punkte, und der mit dem größten
|
||
Gestaltungsspielraum. Zwei Varianten:
|
||
|
||
### Variante A (empfohlen): nur eingestufte Inhalte · 8–10 AT
|
||
|
||
Protokolliert werden Lesezugriffe **ausschließlich** auf Seiten mit
|
||
`classification = VS_NFD`. Setzt P1-2 voraus.
|
||
|
||
- [ ] Instrumentierung der Lesepfade: Seitenansicht, Public-API-GET,
|
||
Attachment-Download, Export, No-JS-Shell, Collab-WS-Join · 4 AT · #222
|
||
- [ ] Dedup-Fenster (eine Sitzung + eine Seite innerhalb N Minuten = ein
|
||
Ereignis), sonst erzeugt Yjs-Sync eine Ereignisflut · 2 AT · #223
|
||
- [ ] Getrennte Tabelle mit eigener Retention und Partitionierung · 2 AT · #224
|
||
- [ ] Abschaltbar, Zweckbindung dokumentiert · 1–2 AT · #225
|
||
|
||
Vorteil über den Aufwand hinaus: Die Zweckbindung ist sauber begründbar
|
||
(„nur eingestufte Inhalte"), was die Personalrats-Diskussion beim Kunden
|
||
erheblich entschärft.
|
||
|
||
### Variante B: alle Lesezugriffe · 18–20 AT
|
||
|
||
Zusätzlich Volumen-, Latenz- und Aufbewahrungsprobleme: gepufferte Schreibung
|
||
ohne Ereignisverlust (ein verlorenes Ereignis ist eine Lücke in der
|
||
Beweissicherung), Suchtreffer als eigene Ereignisklasse, Partitionierung
|
||
zwingend.
|
||
|
||
**Einordnung:** Für „einsetzbar in zugelassener Umgebung" ist das kein
|
||
zwingendes Produktmerkmal — Beweissicherung kann die Plattform erbringen. In
|
||
der Praxis kann Plattform-Logging aber nicht beantworten, _welche eingestufte
|
||
Seite_ gelesen wurde (Proxy-Logs kennen URLs, nicht Einstufungen). In
|
||
Leistungsbeschreibungen taucht das als Muss-Kriterium auf. Deshalb rein —
|
||
aber in Variante A.
|
||
|
||
---
|
||
|
||
## Phase 4 — Verbleibende Roadmap
|
||
|
||
_Meilenstein: `M31 — VS-NfD: backlog`_
|
||
|
||
Nur noch ein Punkt bleibt draußen:
|
||
|
||
- **Plugin-Allowlist mit Hash-Pinning** · 8–10 AT · #232
|
||
Manifest mit SHA-256, Allowlist in `instance_settings`, Prüfung beim Laden,
|
||
Admin-UI. Bleibt zurückgestellt, weil Phase 2 mit der harten Abschaltung das
|
||
Risiko bereits schließt — und weil echte Code-Signierung ohne juristische
|
||
Person ohnehin nicht verfügbar ist. Hash-Pinning ist die richtige Antwort,
|
||
aber nicht die dringendste.
|
||
|
||
---
|
||
|
||
## Phase 5 — Dokumentation · 15–20 AT (vorher 12–15)
|
||
|
||
_Meilenstein: `M30 — VS-NfD: compliance documentation`_
|
||
|
||
Wächst um ca. 25 %, weil jede neue Funktion Handbuch- und Härtungsabschnitte
|
||
nach sich zieht.
|
||
|
||
- [x] **Abgrenzungserklärung §52 VSA** — welche Sicherheitsgrundfunktionen die
|
||
Anwendung _nicht_ erbringt und wem sie zufallen. Wichtigstes
|
||
Einzeldokument. · 3 AT · #226 → `40-abgrenzungserklaerung.md`
|
||
- [x] **Härtungsleitfaden** mit Referenzkonfiguration „VS-NfD-Betrieb":
|
||
lokale Auth aus (Zeile ⏳ bis #216), Public-API aus, MCP aus, Feeds aus,
|
||
Plugins aus, Backup nur lokal · 3 AT · #227 → \`50-haertungsleitfaden.md\`
|
||
- [x] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan,
|
||
Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 → `60-sicherheitsdokumentation.md`
|
||
- [x] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore,
|
||
Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 → `70-betriebshandbuch.md`
|
||
(Airgap-Kapitel verweist auf offene #218–#221)
|
||
- [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung
|
||
„Produkt / Betreiber / nicht anwendbar" · 3–4 AT · #230
|
||
- [x] **Restrisikoliste** mit bewusst offenen Punkten · 1 AT · #231 → `90-restrisiken.md`
|
||
|
||
---
|
||
|
||
## Terminwirkung
|
||
|
||
| Block | vorher | Rev. 2 |
|
||
| ----------------------- | ------------ | ---------------- |
|
||
| Phase 1 Blocker | 22–28 AT | 30–38 AT |
|
||
| Phase 2 Billigblock | 15–18 AT | 22–28 AT |
|
||
| Phase 3 Beweissicherung | — | 8–10 AT (Var. A) |
|
||
| Phase 5 Dokumentation | 12–15 AT | 15–20 AT |
|
||
| **Summe** | **49–61 AT** | **75–96 AT** |
|
||
|
||
Bei 4 produktiven Tagen pro Woche:
|
||
|
||
- **vorher:** ca. 3–3,5 Monate
|
||
- **Rev. 2 mit Variante A:** ca. **4,5–5,5 Monate**
|
||
- **Rev. 2 mit Variante B:** ca. **5,5–6,5 Monate**
|
||
- Plus Phase 4 (Hash-Pinning): weitere ~0,5 Monate
|
||
|
||
Der Zuwachs von ~26–35 AT verteilt sich zu etwa zwei Dritteln auf
|
||
Beweissicherung und Airgap-Verifikation. Beides sind Punkte, nach denen
|
||
gefragt wird — nicht Punkte, die man erklären muss.
|
||
|
||
**Empfehlung zur Reihenfolge:** Termin für den Angebotsstand nicht
|
||
verschieben. Phase 5 und der Billigblock sind nach ca. 3 Monaten fertig — das
|
||
genügt, um Gespräche zu führen. Phase 1-3 laufen dahinter weiter. Ein Angebot
|
||
mit belegter Dokumentation und laufender Umsetzung ist besser als ein
|
||
fertiges Produkt ohne Gesprächspartner.
|
||
|
||
---
|
||
|
||
## Zu klärende Punkte aus der Ist-Aufnahme
|
||
|
||
- [x] **drawio-Plugin**: geklärt — vendored unter
|
||
`packages/plugins/drawio/vendor/`, lädt keine externe Editor-URL;
|
||
kein Ausschlusskriterium (Ist-Aufnahme I-40)
|
||
- [x] Fließen Labels heute in Exporte? **Nein** — `export.service.ts` lädt
|
||
`labelIds` nur für die Permission-Filterung (Ist-Aufnahme I-03a)
|
||
- [ ] Was bricht ohne Internetzugang? → wird durch den Testlauf #220
|
||
beantwortet (Ist-Aufnahme I-28)
|
||
- [x] MCP-Gate-Duplikat: **divergiert nicht** — nutzt den zentralen
|
||
`PermissionService`, eigenständig sind nur die Schalter
|
||
(Ist-Aufnahme I-33)
|
||
- [x] Collab-WebSocket: WS-Ebene prüft nur das Token, aber Tokens leben
|
||
60 s und Grant-Entzug schließt Verbindungen per `pg_notify`;
|
||
Randbedingung für #222 (Ist-Aufnahme I-41)
|
||
|
||
---
|
||
|
||
## Ergänzungen aus der Ist-Aufnahme (2026-07-30)
|
||
|
||
Befunde der nachgezogenen `10-ist-aufnahme.md`, die in diesem Plan
|
||
fehlten — als Issues angelegt:
|
||
|
||
- [x] Conversion-Job-Payloads prunen — Rohbytes jedes Im-/Exports liegen
|
||
unbefristet in `conversion_jobs` · 2 AT · #233 (M24, I-22)
|
||
- [x] Retention für `mail_outbox` — Digest-Mails tragen Seitentitel
|
||
· 1 AT · #234 (M24, I-23)
|
||
- [x] `page_links.target_slug`-Residuum nach Purge entscheiden
|
||
· 0,5 AT · #235 (M24, I-24)
|
||
- [x] Node-Version pinnen — Voraussetzung für #219 · 0,5 AT · #236 (M25, I-26)
|
||
|
||
Ohne eigenes Issue: IndexedDB-Kopie auf Endgeräten (I-25) — als
|
||
Akzeptanzkriterium in #226 (Abgrenzungserklärung) und #231
|
||
(Restrisikoliste) verankert.
|
||
|
||
---
|
||
|
||
## Vorhandene Stärken (im Angebot nach vorne stellen)
|
||
|
||
Nicht ausbauen, sondern **belegen**:
|
||
|
||
- Keine Telemetrie, keine Update-Checks, keine CDNs, kein Runtime-Nachladen,
|
||
Fonts self-hosted, CSP `default-src 'self'` — hier scheitern die meisten
|
||
Konkurrenzprodukte
|
||
- Zentrales, default-closed Berechtigungsmodell mit deny-wins und
|
||
Route-Enumeration-Test — ein prüfbares Artefakt
|
||
- Volltextsuche in Postgres statt externer Suchmaschine
|
||
- Single-Tenant — passt zur empfohlenen Betriebsform „eine Instanz pro
|
||
Einstufungsniveau"
|
||
- Public-API und MCP zur Laufzeit hart abschaltbar, Default aus
|
||
- Open Source unter MIT — Quelloffenheit ist im Prüfprozess ein Vorteil
|
||
- Keine Verschlüsselung im Code = korrekte Architektur, nicht fehlende
|
||
Funktion. So argumentieren.
|