Zusammenfassung aller implementierten Sicherheitsmaßnahmen, Gates und Konfigurationen — als Grundlage für ein externes Security-Review.
Was dieses Dokument ist: eine konsolidierte Selbstauskunft über den aktuellen technischen und organisatorischen Sicherheitsstand — jede Maßnahme ist im Code oder in der Serverkonfiguration real vorhanden und überwiegend automatisiert getestet, nicht nur geplant. Wo eine Maßnahme noch aussteht, ist das explizit als solche markiert statt verschwiegen.
Was dieses Dokument nicht ersetzen kann: einen tatsächlich durchgeführten, unabhängigen externen Penetrationstest. Diese Übersicht ist die Grundlage für ein solches Review, nicht dessen Ergebnis.
bcrypt/Argon2id über UserPasswordHasherInterface, Mindest-Kostenfaktor bewusst festgelegt und getestet (PasswordHashingCostFactorTest) statt Framework-Default zu vertrauen. Echter Fund 2026-09-25: der Argon2id-Dummy-Hash zur Login-Anti-Enumeration war mit dem Default-time_cost auf der realen Server-Hardware messbar schneller als ein echter bcrypt-Login — per externem Timing-Test entdeckt, empirisch (nicht aus Tabellenwerten) auf annähernde Gleichzeitigkeit nachkalibriert.
Derselbe externe Test zeigte einen kleineren, aber messbaren Rest-Unterschied bei forgot-password/self-register (synchroner DB-Lookup nur im Treffer-Fall). Strukturell statt kostenangeglichen gelöst: der komplette asymmetrische Teil (Lookup, Verzweigung, Mailversand) läuft jetzt in einem asynchronen Message-Handler — der Controller selbst leistet für jede E-Mail exakt dieselbe, konstante Arbeit. Live re-verifiziert: Antwortzeit für existierenden vs. frei erfundenen Account nicht mehr unterscheidbar.
Verpflichtend für alle Admin-Rollen, optional für reguläre Accounts. Backup-Codes gehasht, einmalig im Klartext gezeigt. Eigener Fund behoben: TOTP-Testflake per gefrorener Uhr gelöst, Produktions-Toleranz (leeway=0) bewusst unverändert streng gelassen.
Eigene Policy für Login, 2FA, Registrierung, KI-Endpunkte — jeweils mit echter symfony/lock-Synchronisierung (sonst unsynchronisiert unter Last).
Kryptographisch sichere Zufallszahlen (random_bytes()), nie mt_rand(). Zeitsicherer Vergleich (hash_equals()), einheitliche Fehlermeldung unabhängig von Trefferquote.
Nie Ad-hoc-if-Prüfungen. Vollständige Owner/Co-Autor/Gast/Admin-Matrix, vier abgestufte Gast-Stufen, Zwei-Stufen-Entfernung bei Co-Autoren.
Externer Scan fand: /api/docs legte das OpenAPI-Schema (Feldnamen wie canManageUsers) auch für anonyme Besucher offen — kein Datenleck (die eigentlichen Endpunkte waren bereits korrekt geschützt), aber unnötige Struktur-Aufklärung. Jetzt Login-Pflicht für den Dokumentations-Endpunkt.
Co-Owner, befristete Nachfolge-Delegation, Master-Recovery (zwei getrennte physische Geheimnisse + Mehrheitszustimmung) — kein Single Point of Failure bei Verlust des einzigen Admin-Zugangs.
Jeder administrative 2FA-/Passwort-Reset erzeugt einen unveränderlichen (append-only) Audit-Log-Eintrag. Vier-Augen-Prinzip sobald ≥2 Admin-Accounts existieren. E-Mail-Änderung ausschließlich Self-Service, nie über einen Admin-Endpunkt.
Fernsperre bei Geräteverlust (Remote-Wipe-Signal), Idempotency-Keys gegen doppelte Verarbeitung bei Offline-Reconnect — systemweit über #[IdempotencyStrategy] erzwungen, nicht optional pro Endpunkt.
Kapitel-Inhalte und Versions-Snapshots vor dem Schreiben verschlüsselt (Sodium/XChaCha20-Poly1305), gekapselt in einem eigenen Doctrine-Type.
Selbstgehosteter HashiCorp Vault, echte 2-von-3-Unseal-Schwelle (kein Solo-Schlüssel). Restore-Drill real durchgeführt und bestanden, nicht nur konfiguriert.
users.email verschlüsselt gespeichert, zusätzlicher HMAC-SHA256-Hash für Login-/Eindeutigkeitsprüfung — kein Klartext-Vergleich auf sensibler Spalte.
Globaler Doctrine-SQL-Filter schränkt jede Query automatisch auf erlaubte Projekte ein — zusätzlich zur Voter-Prüfung, nicht als Ersatz. Fail-closed-Zweig bewusst ungeloggt (Tripwire).
Anwendungsrolle nur mit DML-Rechten, kein SUPERUSER. Getrennte App-/Batch-Rolle, Batch-User ohne Zugriff auf E-Mail/Passwort-Hash-Spalten.
Statt INT AUTO_INCREMENT — verhindert Enumerierbarkeit von Bestandsgrößen. Ungültiges ID-Format liefert 400, nie 500.
Manuskripte sind serverseitig lesbar (Suche, Kontinuitätsprüfung, Kollaboration erfordern das) — ehrlich als Architekturentscheidung dokumentiert, nie als „Ende-zu-Ende-verschlüsselt" beworben.
Rekursive Allowlist statt HTMLPurifier — TipTap-Inhalte sind JSON, kein roher HTML-String. Unbekannter Node-Typ → vollständige Ablehnung, nie stilles Entfernen. Tiefen-/Größenlimits gegen Payload-Bomben.
Echtes Content-Sniffing (finfo, nie Client-Angabe), harte MIME-Allowlist, Größenlimits, selbst generierte Dateinamen (strukturell kein Pfad-Traversal möglich).
Selbstgehostetes ClamAV (kein SaaS-Anbieter), fail-closed — Scanner nicht erreichbar = Upload abgelehnt, nie ungescannt durchgelassen. Real mit EICAR-Testdatei verifiziert, Speicherlimit gegen Ressourcen-Erschöpfung gesetzt.
Zielgröße jedes Archiv-Eintrags vor dem Entpacken geprüft. LIBXML_NONET explizit aktiviert, DTD-Laden deaktiviert bei jedem XML-Parsing im Import-Pfad.
Jeder serverseitige Outbound-Call gegen eine feste Domain-Allowlist geprüft — kein „Import von beliebiger URL"-Feature, das das umgehen könnte. Export-Rendering-Pipeline ohne Netzwerkzugriff außer eigener Whitelist.
Ausschließlich dedizierte Write-DTOs, nie generische Entity-Hydrierung. Kein Endpunkt mappt einen Request-Body direkt auf eine Entity.
X-Content-Type-Options: nosniff global, Content-Disposition bei jedem Datei-Endpunkt — Tiefenverteidigung zusätzlich zur MIME-Allowlist.
HTTPS erzwungen (nicht nur applikationsseitig), Strict-Transport-Security, restriktive Content-Security-Policy (connect-src 'self' — verhindert Datenexfiltration bei hypothetischem XSS). Kein KI-Provider in der Allowlist. Echter Fund 2026-09-25: galt bisher nur für Antworten, die tatsächlich durch die Backend-Anwendung liefen — die vom Webserver direkt ausgelieferte SPA-Hülle (HTML/Assets) hatte dieselben Header schlicht nicht. Beide Ausliefer-Pfade jetzt mit identischen Werten abgesichert.
Externer Scan-Fund: bislang gar nicht gesetzt. strict-origin-when-cross-origin statt Browser-Default nur implizit zu vertrauen; Permissions-Policy deaktiviert nachweislich ungenutzte Browser-Features (Kamera, Geolocation, Payment …); X-Frame-Options: DENY als Fallback für Browser ohne CSP-Level-2-Support (frame-ancestors deckt moderne Browser bereits ab).
Ausschließlich exakte, hartcodierte Domains bei allow_credentials — nie Wildcard-Regexes über Domain-Familien.
SameSite-Cookie plus Pflicht-Header X-Requested-With bei jedem mutierenden Request.
v-html im Frontend implementiertNirgends im Code verwendet (bewusst, per Kommentar dokumentiert) — Editor-Rendering läuft ausschließlich über TipTaps eigenes, geschlossenes Schema.
Opt-in-Funktion für geteilte Geräte — schnelles Verbergen sensibler Inhalte.
Explizite browserslist-Baseline statt einer impliziten Build-Tool-Annahme. Feature-Detection-Skript (ES5, vor dem eigentlichen Bundle geladen) zeigt bei zu altem Browser einen Hinweis — kein Hard-Block, reine Nutzerfreundlichkeit statt Sicherheitsentscheidung.
Eigene, Docker-verträgliche INPUT-Ketten-Firewall (Default-Deny mit Allowlist) — bewusst nicht die Plesk-Firewall, deren generiertes Skript nachweislich den gesamten Container-Stack vom Netz getrennt hätte.
Reale nftables-Integration (Docker-Host-spezifischer Stolperstein gefunden und behoben), real mit Dummy-IP verifiziert — nicht nur konfiguriert, sondern eine echte Netzwerkregel bei einem Bann bestätigt.
Ausschließlich Key-Auth, kein Passwort-Login.
Web-App und Git-/CI-Server liefen zunächst unter derselben Host-UID (ein RCE in der größeren, exponierten App hätte automatisch Zugriff auf den Git-Server gehabt) — behoben, dedizierte System-User pro Dienst, als generelles Prinzip festgehalten.
Repo-Verzeichnis war versehentlich weltlesbar (für andere, fremde Accounts auf dem geteilten Host) — real mit einem fremden Account nachgewiesen und geschlossen. Backup-Verschlüsselungscode aus der Crontab in eine eigene 0600-Datei ausgelagert.
Kein direkter Push auf main — real getestet, ein Push-Versuch wurde tatsächlich zurückgewiesen. Pflicht-CI-Checks vor jedem Merge.
Automatischer Staging-Deploy nur bei grüner CI — negativer Pfad real getestet (rote Checks lösten nachweislich keinen Deploy aus). Deploy-Token least-privilege (read:repository, kein Schreibzugriff, kein SSH-Key).
Verschlüsselte Backups (Vault, Git-Server, Datenbank), Restore-Drills tatsächlich durchgeführt und bestanden — nicht nur als Skript vorhanden.
Explizites Speicherlimit für den ClamAV-Container, damit ein Ressourcen-Ausreißer nie den geteilten Host statt nur diesen einen Dienst trifft.
Echter Fund bei der Timing-Fix-Umsetzung: kein Prozess konsumierte bislang die asynchrone Nachrichten-Warteschlange — mehrere Nachrichten hingen real unverarbeitet in der Datenbank. Eigener Worker-Container (gleiches Image, messenger:consume), wird durch dasselbe Compose-File automatisch bei jedem Deploy mitgeführt.
Bei der Nachprüfung der eigenen heutigen Änderungen festgestellt: eine geänderte Webserver-Konfiguration (Bind-Mount) wurde vom automatischen Deploy zwar ausgecheckt, aber nie tatsächlich neu geladen, ohne einen zusätzlichen manuellen Schritt — der reine Merkregel-Charakter führte genau einmal zum Vergessen. Jetzt fester, bedingungsloser Teil des Deploy-Skripts, kein manueller Schritt mehr nötig. Zeigt die eigene Prüfdisziplin: der Fund kam aus einer Nachverifikation der eigenen Arbeit, nicht von außen.
Durchgängiges Architekturprinzip, nicht nur bei einem Dienst: ausschließlich EU-Hosting (Hetzner/Strato), keine US-Hyperscaler (AWS/Google Cloud/Azure) und keine US-SaaS-Anbieter für sicherheitsrelevante Funktionen — wegen des US CLOUD Act (extraterritorialer Datenzugriffs-Anspruch, unabhängig vom physischen Speicherort).
Technische Erfüllung von Art. 20 DSGVO — auf jedem Plan kostenlos, darf laut Grundsatz nie zum Bezahl-Feature werden. Konto- und projektbezogene Daten getrennt, jeweils vollständig (mehrfach real gegen alle ~170 Entitätstypen abgeglichen).
Jeder Gast-Zugriff auf freigegebene Inhalte wird protokolliert und ist für Owner/Co-Autoren einsehbar.
Art. 17 Abs. 3 lit. b DSGVO — eng begrenzte Ausnahme nur bei offenem Rechenschaftspflicht-Vorgang, nie eine pauschale Verzögerung der gesamten Löschung.
Gast-Accounts erhalten nur, was für die konkrete Freigabe nötig ist. Nie angenommene Einladungen werden nach 14 Tagen automatisch anonymisiert.
Keine Werbe-/Analytics-Cookies, kein Cross-Site-Tracking. Performance-Telemetrie nie still im Hintergrund — jede Übertragung erzeugt eine sichtbare Meldung.
Sicherheitsanforderungen sind in einem einzigen, verbindlichen Regelwerk konsolidiert (illumentis-security-testing-lawbook.md, ~1000 Zeilen) — additiv-only: es wird nie etwas gekürzt oder abgeschwächt, ohne explizite Rückfrage. Jede sicherheitsrelevante Maßnahme wird durch eine automatisierte Testklasse validiert, nicht durch manuelle Prüfung allein.
FunctionalCoverageManifestTest, feste Feature-ID-Whitelist statt Ehrenwort)main (Backend- und Frontend-Checks, inkl. composer audit/npm audit)TotpRateLimitSubscriber), dessen getroffener Zweig von der tatsächlichen Ausführungsgeschwindigkeit unter paralleler Testlast abhängt. Beide Werte liegen deutlich über der 90%-Schwelle, hier bewusst als Messunsicherheit benannt statt den günstigeren Wert unkommentiert zu übernehmen.14 Angriffsszenarien geprüft — 13 davon mit konkreten automatisierten Tests verifiziert. Identifizierte Schwachstellen wurden behoben und erneut getestet, eine bekannte Einschränkung ist dokumentiert statt verschwiegen. Aktueller Testlauf: 3.896 automatisierte Tests, 2 Testfehler im letzten Lauf, 1 bekannter unabhängiger Risky-Test wird separat verfolgt.
| Angriffsszenario | Ergebnis |
|---|---|
| Compromised User | bestanden |
| Cross-Tenant IDOR | bestanden |
| Compromised Co-Author | bestanden |
| Compromised Editor | bestanden |
| Compromised Admin | bestanden — mit dokumentierten Grenzen |
| Session Hijacking | bestanden — mit dokumentierten Grenzen |
| Race Conditions | bestanden |
| API Manipulation | bestanden |
| Vault Policy Bypass | bestanden |
| Authenticated Scraping | bestanden |
| Branch/Merge Manipulation | bestanden |
| Permission Escalation | bestanden |
| Compromised Symfony Runtime | bestanden — mit dokumentierten Grenzen |
| Deployment Integrity | bekannte verbleibende Maßnahme |
Die beschriebenen Tests stellen keine Garantie gegen zukünftige Sicherheitslücken dar. Sie prüfen definierte Sicherheitseigenschaften unter den dokumentierten Voraussetzungen — die vier folgenden Fälle wurden bewusst mit ihren jeweiligen Grenzen dokumentiert statt als vollständig gelöst dargestellt.
Das Prinzip „kein Admin sieht Buchinhalte" gilt vollständig für den App-Admin (can_manage_users) über die Anwendung selbst (Voter, Serializer Groups, Field-Level Encryption). Es gilt NICHT gegen einen Server-Root-/Infrastruktur-Admin: der kann den KEK aus Symfony Secrets/der Umgebungsvariable auslesen. Echte Wirksamkeit auch gegen kompromittierten Root-Zugriff bräuchte einen externen KMS/Vault, der selbst entschlüsselt (Chiffretext rein, Klartext raus, Schlüssel verlässt den Key-Server nie) — für content/media/email-hash bereits umgesetzt (siehe Vault Policy Bypass/Compromised Symfony Runtime), aber noch nicht für jeden Datenpfad.
Die Erkennung vergleicht bewusst nur den User-Agent, nicht die IP-Adresse (ein Netzwerkwechsel mitten in einer legitimen Sitzung ist normal). Ein Angreifer, der IP UND User-Agent des Opfers exakt nachbildet, bleibt unentdeckt.
Ein kompromittierter, aktiv laufender Symfony-Prozess kann zum Zeitpunkt des Zugriffs im Prozessspeicher vorhandene Klartext-Schlüssel bzw. bereits entschlüsselte Daten erreichen. Die Vault-Architektur (Envelope Encryption) reduziert insbesondere das Risiko eines dauerhaft verfügbaren, statischen Anwendungsschlüssels — sie verhindert keinen vollständigen Live-RCE.
File-Integrity-Monitoring (Erkennung nachträglicher Code-Manipulation) existiert und ist getestet. Automatisierte Deployment-Härtung (signiertes Manifest, SHA-Pinning, externe Out-of-Band-Verifikation) ist dokumentiertes Wissen, aber noch keine laufende Maßnahme.
Aus dem Lawbook-Abschnitt „Externe Prüfbarkeit" übernommen — Selbstauskunft ist keine externe Bestätigung. Diese Tabelle unterscheidet bewusst zwischen beidem.
| Bereich | Status |
|---|---|
| Auth, Autorisierung, IDOR, Verschlüsselung, Input-Validierung | Vollständig abgedeckt, automatisiert getestet |
| Netzwerk/Transport, DNS-/E-Mail-Authentifizierung (SPF/DKIM/DMARC) | Abgedeckt |
| Backup/Disaster-Recovery, Incident-Response-Plan | Abgedeckt, Restore real getestet |
| Secret-Scanning in CI (Gitleaks) | Implementiert (2026-09-25) — echter Konfigurationsfehler beim Aufbau real gefunden und behoben (fehlendes useDefault=true hätte alle Regeln stillschweigend deaktiviert) |
| Lizenzprüfung gegen Allowlist (Abhängigkeiten) | Implementiert (2026-09-25), Backend + Frontend, fail-closed bei fehlender Lizenzangabe |
| Automatisierte Coverage-Schwellen-Erzwingung | Implementiert (2026-09-26) — PCOV in CI, Schwellen nach echtem Messlauf auf 90%/80% angehoben. Alle 101 dabei gefundenen Lücken in der Security-Gruppe systematisch mit echten Tests geschlossen (nicht nur die Schwelle erreicht) — Ist-Stand aktuell 99,4–99,6%/87,5–87,7% (kleine Messwert-Schwankung zwischen Läufen, siehe Abschnitt 8), beide Werte deutlich über der Schwelle. Vier konstant reproduzierte, dokumentierte Zeilen bleiben offen: zwei echter Dead-Code, zwei wegen eines externen Vault-Rate-Limits |
| Rate-Limit-Backoff der Vault-E-Mail-Hash-Migration | Implementiert (2026-09-26) — Self-Throttle + Retry mit exponentiellem Backoff gegen Vaults Transit-Rate-Limit (60/60s), zuvor gänzlich unbewusst. Vollständig getestet über eine injizierbare Testnaht, ohne das reale Rate-Limit-Budget zu belasten |
| Mutation-Testing (Voter-Klassen + ChapterContentSanitizer) | Implementiert (2026-09-26) — monatlicher Reporting-Cron (Infection), kein CI-Gate. Erster echter Lauf: 596/773 Mutanten getötet, Covered Code MSI 77% |
/.well-known/security.txt + Vulnerability Disclosure Policy (Safe Harbor) | Implementiert (2026-09-29) — echte Kontaktadresse, dedizierte Policy-Seite |
| Malware-Scan für JSON-Import-ZIP-Inhalte | Bewusst außen vor — eigener Zip-Bomb-Schutz vorhanden |
| Dedizierter Produktivserver (aktuell: geteilter Strato-Host für Beta) | Hetzner-Umzug geplant, noch nicht live |
| Unabhängiger externer Penetrationstest | Noch nicht stattgefunden |
| Zertifizierung des Hosters (ISO 27001 o. ä.) | Nur implizit über EU-Standort-Entscheidung |