dorfteich/docs/vs-nfd/20-massnahmenplan.md
Claude Fable 5 4af5e6e81f
All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 6m2s
CI / Build container images (pull_request) Successful in 4m1s
CI / Auth e2e pack (pull_request) Successful in 8m26s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Build and push images (push) Successful in 18s
CD / Deploy to Test (push) Successful in 15s
CD / Smoke tests against Test (push) Successful in 1m23s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 6m6s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m25s
CI / Import/export fidelity gate (push) Successful in 1m0s
#225: read-trail master switch and written purpose limitation
New instance switch readTrail.enabled, default OFF: read logging is
employee monitoring in a works council's eyes — an ordinary instance
must not surveil reads. Off means no event is written ANYWHERE (no row,
no stdout line, verified by test); the api announces the switch position
once per boot, so an eventless trail is never ambiguous — a gap reads
as "was off", never "was lost".

The written purpose limitation ships as section 7 of the VS-NfD
security documentation (#228): what is recorded (no content, no titles,
no IPs, no fingerprinting), why (evidence for reads of marked content
only — variant A is the technical anchor of the promise), who may read
it (Site Admin, API-only), for how long (readTrail.retentionDays,
audited pruning), and what it may NOT be used for (no performance or
behaviour monitoring). The hardening guide's reference configuration
turns the trail on (reference value true) and points to that text; the
existing trail suites now enable the switch explicitly.

Refs #225.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUtYMxwTCMHG9mVHnwbFg8
2026-07-31 12:35:18 +02:00

13 KiB
Raw Permalink 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 (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 · 45 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" · 34 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 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.