Technische und organisatorische Maßnahmen
Nach Art. 32 DSGVO. Anlage 2 zum Vertrag zur Auftragsverarbeitung (AGB, Anlage 2), Stand 08.09.2026.
Gegliedert nach Art. 32 Abs. 1. Jede Zeile beschreibt, was wirklich eingerichtet ist, mit der Stelle, an der man es nachprüfen kann. Was fehlt, steht in Abschnitt 6 und nicht im Kleingedruckten.
1. Verschlüsselung und Pseudonymisierung (lit. a)
- Transport: HTTPS erzwungen, Caddy mit Let's-Encrypt-Zertifikat,
Strict-Transport-Security: max-age=31536000; includeSubDomains. Kein Klartext-HTTP. - Backups: restic verschlüsselt clientseitig. Das Repository liegt seit 07.09.2026 auf einer Hetzner Storage Box, also nicht mehr auf derselben Platte wie die Daten (4.4). Dort liegen keine Klardaten, weil verschlüsselt wird, bevor etwas den Server verlässt. Der Schlüssel liegt auf dem Server und zusätzlich offline.
- Passwörter: bcrypt mit Kostenfaktor 10, nie im Klartext gespeichert. Das Erstpasswort entsteht aus
randomBytes(12)und wird nirgends abgelegt. - Pseudonymisierung findet nicht statt und wäre hier sinnlos: der Makler muss den Namen seines Kunden lesen können, das ist der Zweck der Anwendung.
- Keine Verschlüsselung im Ruhezustand auf Datenbankebene, siehe 4.6.
2. Vertraulichkeit (lit. b)
- Zutritt: Rechenzentrum Hetzner, nach eigenen ISO-27001-Zertifikaten. Kein eigener Serverraum.
- Zugang: Der einzige von außen erreichbare Dienst ist die Anwendung selbst. Offen sind Port 22, 80 und 443, durchgesetzt von der Hetzner Cloud Firewall vor dem Host, weil Docker seine Regeln sonst an
ufwvorbeischreibt. MySQL hat keinen veröffentlichten Port und ist nur im Docker-Netz erreichbar; nachgeprüft mitss -tlnH(DEPLOYMENT §6). - SSH nur mit Schlüssel, Passwort-Anmeldung und Root-Login abgeschaltet.
- Anmeldung: E-Mail und Passwort. Nach 10 Fehlversuchen je Viertelstunde sperrt die Bremse in
lib/domain/bremse.ts, gezählt über den letztenX-Forwarded-For-Eintrag, den ein Client nicht fälschen kann. Dasselbe gilt für „Passwort vergessen", mit eigenem Zähler. - Mehrere Konten je Instanz, ihre Zahl begrenzt
SITZE. Alle sehen dasselbe, es gibt keine Rollen: es ist ein Büro, kein Mandant. Ein entferntes Konto ist sofort ausgesperrt, weil es keine Session-Tabelle gibt, prüftlib/auth.tsbei jeder Anfrage, ob das Konto noch besteht; ohne das trüge ein ausgestelltes Token einen ausgeschiedenen Mitarbeiter bis zu dreißig Tage weiter. - Trennung der Makler: eigene Datenbank, eigener MySQL-Benutzer mit Rechten nur darauf, eigener Container, eigenes Upload-Verzeichnis, eigenes
AUTH_SECRET. Das letzte ist die entscheidende Zeile: bei geteiltem Secret wäre ein Sitzungs-Token aus Instanz A auch bei Instanz B gültig, und die Trennung fiele vollständig. - Keine Auftragsdatenverarbeitung durch Menschen: Zugriff auf Klardaten hat allein Brainsight AI Consulting e.U. zu Administrations- und Wiederherstellungszwecken. Es gibt keine Support-Mitarbeiter, keine Subunternehmer mit Datenzugriff.
3. Integrität (lit. b)
- Eingaben werden am Rand geprüft (Zod in
lib/validation/), Datenbankzugriffe laufen ausschließlich über Prisma mit gebundenen Parametern. Keine zusammengesetzten SQL-Zeichenketten. - Jede Statusänderung schreibt einen Protokolleintrag in derselben Transaktion (
Aktivitaet), ein Stand kann sich also nicht unbemerkt bewegen. - Hochgeladene Dateien bekommen einen serverseitig erzeugten Namen; der Name des Nutzers ist nur Anzeigetext und wird nie Teil eines Pfades. Ausgeliefert wird über die Datenbank-Id, nie über einen Pfad aus der URL, mit
X-Content-Type-Options: nosniff. SVG ist als Logo nicht zugelassen, weil es Skript tragen kann. - Der CSV-Export ist gegen Formelinjektion entschärft (
lib/export/formel.ts): ein Freitext, der mit=,+,-oder@beginnt, bekommt einen Apostroph vorangestellt und wird von Excel als Text gelesen, nicht als Formel. Zahlen und Daten bleiben unangetastet, sonst rechnete niemand mehr mit ihnen. Das XLSX braucht die Behandlung nicht: dort ist eine Formel ein eigener Zelltyp. - Der Seed, der alle Tabellen leert, bricht bei
NODE_ENV=productionab. Neue Instanzen entstehen mitprisma/provision.ts, das kein einzigesdeleteenthält.
4. Verfügbarkeit und Belastbarkeit (lit. b, c)
- Tägliche Sicherung um 03:00, seit 07.09.2026 in Betrieb:
mysqldump --single-transactionje Datenbank, dazuuploads/und die Instanz-.env, verschlüsselt nach restic. Aufbewahrung 14 Tage täglich, 8 Wochen wöchentlich. Das Skript bricht ab, wenn ein Dump keine einzige Tabelle enthält: eine still fehlschlagende Sicherung ist schlechter als keine, weil man sich auf sie verlässt. - Wiederherstellbarkeit ist belegt, nicht behauptet: am 07.09.2026 vollständig durchgespielt, Snapshot in eine Wegwerf-Datenbank eingespielt und gegen das Original gezählt (Tabellen, Konten, Profil). Dauer drei Sekunden bei einer frisch angelegten Instanz; die Zahl wächst mit den Daten und ist bei jedem Größensprung neu zu messen.
- Das Repository liegt seit 07.09.2026 nicht mehr auf demselben Server, sondern auf einer Hetzner Storage Box (
sftp:storagebox:/home/cockpit, Aufbewahrung dort--keep-daily 14 --keep-weekly 8). Die vorhandenen Snapshots sind perrestic copymitgezogen, das Rückspielen von dort ist geprüft (DEPLOYMENT §10). - Was die Sicherung damit abdeckt: versehentliches Löschen, eine zerlegte Migration und den Verlust des Servers. Nicht abgedeckt bleibt der Verlust des Restic-Passworts; es liegt als Zweitschrift außerhalb des Servers.
- Automatische Sicherheitsupdates des Betriebssystems, Neustart im Wartungsfenster.
- Healthcheck unter
/api/health, Container mitrestart: unless-stopped.
5. Verfahren zur regelmäßigen Überprüfung (lit. d)
- Vor jedem Ausrollen laufen Typprüfung, die Unit-Tests (
npm test, zum Stichtag 131) und der Produktionsbau; die Migration wird vor dem Umschalten angewandt, ein Fehlschlag lässt die alte Fassung weiterlaufen (DEPLOYMENT §11). - Sicherheitsdurchsicht des Codes in security-review, fortgeschrieben mit dem Stand der Behebung.
- Portscan von außen nach jeder Änderung an Firewall oder Compose-Datei.
- Abnahmeliste je Instanz (DEPLOYMENT §9).
6. Was diese TOMs nicht behaupten
Eine TOM-Liste, die nur Erfülltes aufzählt, ist im Ernstfall wertlos. Offen sind:
| Lücke | Einordnung | Vorgesehen |
|---|---|---|
| Keine Verschlüsselung im Ruhezustand. Hetzner-Volumes sind nicht standardmäßig verschlüsselt; die Datenbank liegt im Klartext auf der Platte. | Schützt nicht gegen den Ausbau der Platte im Rechenzentrum. Gegen den Zugriff über das Netz schützen 4.2 und 4.4. | Bewusste Entscheidung gegen LUKS (BETRIEB §6): eine Passphrase bei jedem Neustart hieße, dass das nächtliche Backup nach einem Neustart still ausfällt. Feldverschlüsselung der IBAN, wenn ein Makler es verlangt. |
| Keine Mehrfaktor-Anmeldung. | Wenige Konten je Instanz, Erstpasswort aus Zufallszeichen, Bremse nach 10 Versuchen. | Nachrüstbar, wenn ein Makler es fordert. |
| Keine Rollen und Rechte. Jedes Konto einer Instanz sieht und ändert alles. | Es sind die Kolleginnen eines Büros, nicht getrennte Mandanten; der Bestand ist ihr gemeinsamer. Das Protokoll (Aktivitaet.akteur) hält fest, wer was getan hat. | Rollen erst, wenn ein Makler danach fragt (BEZAHLUNG §12). |
| Keine Content-Security-Policy. | Die übrigen Header sind gesetzt; eine CSP braucht Nonces für die Inline-Skripte von Next und ist ein eigener Umbau. | BETRIEB §6. |
Kein zentrales Sicherheitsprotokoll und keine Alarmierung. Fehlversuche und Serverfehler landen in docker logs. | Ein Angriff bliebe möglicherweise unbemerkt. | BETRIEB W5: Cron, der die Logs durchsieht und meldet. |
| ~~Die Sicherung liegt auf derselben Platte wie die Daten.~~ Erledigt am 07.09.2026: restic schreibt auf eine Hetzner Storage Box. | Der Zugang der Box ist auf append-only nicht eingeschränkt; ein Trojaner mit Root-Rechten auf dem Server könnte die Snapshots löschen. | restic mit einem Append-only-Schlüssel der Box, wenn ein Anlass entsteht. |
| Kein Malware-Scan der Uploads. | Hochladen darf nur der angemeldete Makler; ausgeliefert wird mit nosniff. | Später, mit Anlass. |
Diese Liste gehört dem Makler vor der Unterschrift gezeigt. Ein Verantwortlicher, der nach Art. 28 Abs. 1 „hinreichende Garantien" beurteilen soll, kann das nur mit ihr.