dorfteich/docs/vs-nfd/20-massnahmenplan.md
Claude Fable 5 488d0d06f1
Some checks failed
CI / Lint, typecheck, test (pull_request) Successful in 5m40s
CI / Build container images (pull_request) Successful in 4m34s
CI / Auth e2e pack (pull_request) Successful in 9m7s
CI / Import/export fidelity gate (pull_request) Successful in 1m0s
CD / Deploy to Test (push) Blocked by required conditions
CD / Smoke tests against Test (push) Blocked by required conditions
CD / Promote to Int (push) Blocked by required conditions
CI / Auth e2e pack (push) Blocked by required conditions
CI / Import/export fidelity gate (push) Blocked by required conditions
CI / Build container images (push) Blocked by required conditions
CD / Build and push images (push) Has been cancelled
CI / Lint, typecheck, test (push) Has been cancelled
#205: classification inherits down the tree; lowering is a guarded, audited act
New pages take max(instance default, parent level); moving a subtree
under a higher-classified parent raises every member below that level.
No move-like path (reposition, trash-promote, purge-promote) lowers a
level as a side effect — pinned by test. Raising is ordinary editorial
work; lowering requires the dedicated capability canLowerClassification
(pond-wide Pond Admin) in the central permission model. Both directions
are audited (page.classification_raised/_lowered, catalogue v1.1) with
old value, new value, actor and page.

Co-Authored-By: Claude Fable 5 (1M context) <noreply@anthropic.com>
2026-07-31 06:12:26 +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`_
- [x] Enum-Feld `classification` an `Page`, Migration, Default aus
Instance-Setting · 2 AT · #204
- [x] 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".
- [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) · 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.
- [x] **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.