dorfteich/docs/vs-nfd/20-massnahmenplan.md
Claude Fable 5 db4f517e44
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 5m32s
CI / Build container images (pull_request) Successful in 1m13s
CI / Auth e2e pack (pull_request) Successful in 8m22s
CI / Import/export fidelity gate (pull_request) Successful in 57s
CD / Build and push images (push) Successful in 16s
CD / Deploy to Test (push) Successful in 56s
CD / Smoke tests against Test (push) Successful in 1m24s
CD / Promote to Int (push) Successful in 52s
CI / Lint, typecheck, test (push) Successful in 5m36s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m3s
CI / Import/export fidelity gate (push) Successful in 57s
#203: pin all third-party deploy images by digest
The four third-party images in the deploy compose (postgres, pandoc,
gotenberg — previously a floating MAJOR tag —, caddy) are now
name:tag@sha256 pins; the tag stays for readability, the digest decides
what runs. The pinned digests are exactly what the stages already run
(verified against the live containers' RepoDigests on ONE), so the next
recreation is byte-identical. A new early CI step fails on any
third-party compose image without a digest; compose.dev.yml is a local
convenience and deliberately exempt (its node helpers now follow the
#236 pin). Update + rollout procedure in deploy/stages.md — CD does not
sync stage composes, so the hand rollout to test/int/prod is part of
this issue's definition of done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
2026-07-31 05:13:13 +02:00

13 KiB
Raw Blame History

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 M24M31 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 3038 AT

P1-1 Fremdauthentisierung + lokale Auth abschaltbar · 1012 AT

Meilenstein: M27 — VS-NfD: external authentication

  • OIDC Authorization Code + PKCE gegen UserIdentity.provider (ADR 0007 ausbauen), Keycloak als Referenz-IdP · 56 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 · 23 AT · #217

P1-2 Einstufung als First-Class-Metadatum · 1418 AT

Meilenstein: M26 — VS-NfD: classification metadata

  • 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 · 812 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) · 23 AT · #209
    • Markdown-ZIP (Frontmatter + Aufdruck) · 1 AT · #210
    • Atom-Feeds, Public-API, Suchergebnisse, No-JS-Shell · 23 AT · #211
    • Attachment-Download (Dateiname-Präfix + Begleitdatei) · 12 AT · #212
  • Warnung/Sperre beim Anhängen an eingestufte Seiten · 1 AT · #213

P1-3 Verifizierter Offline-/Airgap-Pfad · 810 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) · 23 AT · #219
  • Testlauf in netzisolierter Umgebung, Protokoll als Beleg · 2 AT · #220
  • Offline-Update-Pfad inkl. Migrationen · 23 AT · #221

Phase 2 — Billig, hohe Prüfer-Signalwirkung · Summe 2228 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_SECRET per HKDF (zweckgebundene Subkeys) — echter Fund, vor allen Features · 12 AT · #188
  • Eigenbau-HMAC-JWT durch jose ersetzen · +23 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 56 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 · 12 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.deletedAt nutzen 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 · 12 AT · #202
  • deploy/compose/.env prüfen, Beispieldatei statt Realdatei · 0,5 AT · #198
  • Attachment-Integritätshashes · +23 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 · +34 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 · 820 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 · 810 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 · 12 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 · 1820 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 · 810 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 · 1520 AT (vorher 1215)

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, 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 · 45 AT · #229
  • Zuarbeit IT-Grundschutz APP.3.1 und CON.11.1, je Anforderung „Produkt / Betreiber / nicht anwendbar" · 34 AT · #230
  • Restrisikoliste mit bewusst offenen Punkten · 1 AT · #231

Terminwirkung

Block vorher Rev. 2
Phase 1 Blocker 2228 AT 3038 AT
Phase 2 Billigblock 1518 AT 2228 AT
Phase 3 Beweissicherung 810 AT (Var. A)
Phase 5 Dokumentation 1215 AT 1520 AT
Summe 4961 AT 7596 AT

Bei 4 produktiven Tagen pro Woche:

  • vorher: ca. 33,5 Monate
  • Rev. 2 mit Variante A: ca. 4,55,5 Monate
  • Rev. 2 mit Variante B: ca. 5,56,5 Monate
  • Plus Phase 4 (Hash-Pinning): weitere ~0,5 Monate

Der Zuwachs von ~2635 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? Neinexport.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)
  • 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.