Adds docs/operations/update-runbook.md (obtain, verify by digest, back up, apply, verify, roll back) with the migration behaviour stated explicitly: a failed migration rolls back its own transaction but is recorded in _prisma_migrations and blocks every further migrate deploy (P3009) — including a re-deployed old image — until migrate resolve --rolled-back; semantically irreversible migrations have exactly one way back, the pre-update backup set. No rolling updates on a compose stage. Rehearsed in the isolated environment of #220: regular update to a v2 image set, then a deliberate failed-update (P3018 division by zero, schema change proven rolled back) with image-rollback-alone shown insufficient and the documented recovery executed. Protocol: docs/vs-nfd/98-update-rollback-protokoll.md. ADR 0024 decisions 5+6 recorded as executed; operations handbook and restore runbook updated; plan checkbox P1-3 ticked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
13 KiB
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 = falseinkl. 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
- Enum-Feld
classificationanPage, 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.tsHeader/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".
- 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
→
97-isolationslauf-protokoll.md - Offline-Update-Pfad inkl. Migrationen · 2–3 AT · #221
→
docs/operations/update-runbook.md+98-update-rollback-protokoll.md
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
- Schlüsseltrennung
COLLAB_TOKEN_SECRETper HKDF (zweckgebundene Subkeys) — echter Fund, vor allen Features · 1–2 AT · #188 - Eigenbau-HMAC-JWT durch
joseersetzen · +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. - CSRF fail-closed — fehlendes Origin und Referer wird derzeit durchgelassen · 1 AT · #189
- Session-Timeout konfigurierbar, Default deutlich unter 30 Tagen, separates Idle-Timeout · 1–2 AT · #190
- Feed-Token raus aus dem Query-Parameter, alternativ Feeds hart abschaltbar · 2 AT · #191
- Backup-Ziele einschränkbar — Allowlist, WebDAV/rsync per Deploy vollständig deaktivierbar · 2 AT · #192
- Pond-Purge implementieren — getrashte Ponds bleiben ewig liegen · 3 AT · #193
- Orphan-File-Sweep implementieren,
Attachment.deletedAtnutzen oder entfernen · 2 AT · #194 - Papierkorb aus dem Suchindex entfernen statt query-seitig filtern · 2 AT · #195
- Retention-Job für
audit_log· 1 AT · #196 - Security-Header (helmet), CORS explizit restriktiv · 1 AT · #197
- SBOM in CI (CycloneDX/syft) + Lizenzreport als Artefakt · 1–2 AT · #202
deploy/compose/.envprüfen, Beispieldatei statt Realdatei · 0,5 AT · #198- 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.
- 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. - 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.
- 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 (Zeile ⏳ bis #216), Public-API aus, MCP aus, Feeds aus, Plugins aus, Backup nur lokal · 3 AT · #227 → `50-haertungsleitfaden.md`
- Sicherheitsdokumentation: Architektur, Datenflüsse, Netzplan,
Ports/Dienste, Vertrauensgrenzen · 4 AT · #228 →
60-sicherheitsdokumentation.md - 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 →
80-grundschutz-mapping.md - 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
- drawio-Plugin: geklärt — vendored unter
packages/plugins/drawio/vendor/, lädt keine externe Editor-URL; kein Ausschlusskriterium (Ist-Aufnahme I-40) - Fließen Labels heute in Exporte? Nein —
export.service.tslädtlabelIdsnur für die Permission-Filterung (Ist-Aufnahme I-03a) - Was bricht ohne Internetzugang? Nur die Mail-Zustellung — der
Testlauf #220 zeigt null Egress-Pakete außer dem SMTP-Versuch;
Beleg:
97-isolationslauf-protokoll.md(Ist-Aufnahme I-28) - MCP-Gate-Duplikat: divergiert nicht — nutzt den zentralen
PermissionService, eigenständig sind nur die Schalter (Ist-Aufnahme I-33) - 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:
- Conversion-Job-Payloads prunen — Rohbytes jedes Im-/Exports liegen
unbefristet in
conversion_jobs· 2 AT · #233 (M24, I-22) - Retention für
mail_outbox— Digest-Mails tragen Seitentitel · 1 AT · #234 (M24, I-23) page_links.target_slug-Residuum nach Purge entscheiden · 0,5 AT · #235 (M24, I-24)- 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.