All checks were successful
CI / Lint, typecheck, test (pull_request) Successful in 4m52s
CI / Build container images (pull_request) Successful in 3m54s
CI / Auth e2e pack (pull_request) Successful in 8m4s
CI / Import/export fidelity gate (pull_request) Successful in 56s
CD / Build and push images (push) Successful in 19s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 11s
CI / Lint, typecheck, test (push) Successful in 5m0s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 7m41s
CI / Import/export fidelity gate (push) Successful in 56s
BACKUP_ALLOWED_TARGETS (comma-separated destination hosts) constrains where backups may go, enforced twice: the api rejects settings writes and connection tests towards non-allowlisted hosts with admin-visible error codes and resolves a non-allowlisted configured target to null, and the sidecar enforces the same policy at the point of egress for the WebDAV upload and the rsync mirror alike (shared policy helpers in packages/shared/src/backup-target-policy.ts). BREAKING: the empty default disables every remote target - backups stay local only, the VS-NfD reference configuration (ADR 0026). Existing deployments with a remote target must list its host or uploads and mirror stop. The admin UI distinguishes unavailable-by-policy from unconfigured (i18n de+en) and shows the permitted hosts. Refs #192 (ADR 0026) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168Ph5uBmHm8X28CSVpbpnJ
282 lines
13 KiB
Markdown
282 lines
13 KiB
Markdown
# 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 = false`** inkl. 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 `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 · 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.ts` Header/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
|
||
- [ ] Offline-Update-Pfad inkl. Migrationen · 2–3 AT · #221
|
||
|
||
---
|
||
|
||
## 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`_
|
||
|
||
- [x] **Schlüsseltrennung `COLLAB_TOKEN_SECRET`** per HKDF (zweckgebundene
|
||
Subkeys) — echter Fund, vor allen Features · 1–2 AT · #188
|
||
- [x] **Eigenbau-HMAC-JWT durch `jose` ersetzen** · +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.
|
||
- [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 · 1–2 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
|
||
- [ ] **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 · 1–2 AT · #202
|
||
- [ ] `deploy/compose/.env` prü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.
|
||
|
||
- [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 · 4–5 AT · #229
|
||
- [ ] **Zuarbeit IT-Grundschutz** APP.3.1 und CON.11.1, je Anforderung
|
||
„Produkt / Betreiber / nicht anwendbar" · 3–4 AT · #230
|
||
- [ ] **Restrisikoliste** mit bewusst offenen Punkten · 1 AT · #231
|
||
|
||
---
|
||
|
||
## 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
|
||
|
||
- [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:
|
||
|
||
- [ ] 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.
|