# 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 - [ ] Vererbung im Seitenbaum, Herabstufung nur mit eigenem Recht + Audit · 3 AT · #205 - [ ] Durchreichen in alle Ausgabekanäle · 8–12 AT · #206–#212 - Web-Ansicht (Kopf/Fuß) · 1 AT · #206 - **Print-CSS** (`@media print`, Kopf/Fuß je Seite) — fehlt komplett · 1 AT · #207 - PDF via gotenberg (`pdf-html.ts` Header/Footer-Template) · 1 AT · #208 - DOCX/ODT via pandoc (Reference-Doc mit Kopf-/Fußzeile) · 2–3 AT · #209 - 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` - [ ] **Härtungsleitfaden** mit Referenzkonfiguration „VS-NfD-Betrieb": lokale Auth aus, Public-API aus, MCP aus, Feeds aus, Plugins aus, Backup nur lokal · 3 AT · #227 - [ ] **Sicherheitsdokumentation**: Architektur, Datenflüsse, Netzplan, Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 - [ ] **Betriebshandbuch**: Installation (inkl. Airgap), Update, Backup/Restore, Löschung und Vernichtung, Rollentrennung · 4–5 AT · #229 - [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung „Produkt / Betreiber / nicht anwendbar" · 3–4 AT · #230 - [ ] **Restrisikoliste** mit bewusst offenen Punkten · 1 AT · #231 --- ## 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.