dorfteich/docs/vs-nfd/20-massnahmenplan.md
Claude Fable 5 c4c84b33f9
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m38s
CI / Build container images (pull_request) Successful in 4m11s
CI / Auth e2e pack (pull_request) Successful in 8m55s
CI / Import/export fidelity gate (pull_request) Successful in 1m9s
CI / Import/export fidelity gate (push) Blocked by required conditions
CD / Build and push images (push) Successful in 20s
CD / Deploy to Test (push) Failing after 51s
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
CI / Lint, typecheck, test (push) Successful in 5m37s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Has been cancelled
#200: hard instance-wide plugins.enabled kill switch
plugins.enabled (instance setting, default on — plugins predate the
switch; the VS-NfD reference configuration turns it off) makes every
plugin surface answer 404 via a shared guard: Site-Admin
install/list/mode, pond activation and plugin list, the sandbox frame
and asset routes. The dropzone watcher quarantines drops instead of
installing. Deliberately NOT guarded: the authenticated
fallback-metadata route — it serves no plugin code and existing
plugin_block nodes need it to render their declared fallback (an image
fallback degrades to the neutral placeholder while off, because its
bytes live on the disabled asset surface). The editor offers no plugin
blocks because the pond plugin list is one of the 404ing surfaces.
Admin settings panel gets the toggle (i18n de+en) with the documented
api-restart note (in-process settings cache).

Answers "code execution inside the zone?" with one verifiable
off-switch instead of per-plugin trust machinery (#232, ADR 0025).

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

282 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 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`_
- [x] **Schlüsseltrennung `COLLAB_TOKEN_SECRET`** per HKDF (zweckgebundene
Subkeys) — echter Fund, vor allen Features · 12 AT · #188
- [x] **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.
- [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 · 12 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 · 12 AT · #202
- [x] `deploy/compose/.env` prüfen, Beispieldatei statt Realdatei · 0,5 AT · #198
- [x] **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.
- [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.
- [ ] **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.
- [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 · 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
- [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.