Bedrohungsmodell¶
Zusammenfassung¶
- WebApp ohne Docker-Socket und ohne frei ausführbare Host-Befehle
- HttpOnly-, SameSite-Strict-Sitzung und Same-Origin-Prüfung für Schreibzugriffe
- Session-Inventar mit gezielter Sitzungs-Beendigung, Rate-Limiting und Audit-Log für Anmeldung und Passwort-Recovery
- Core und Updater nur in internen Docker-Netzen und mit Bearer-Token
- Unbound read-only, non-root, ohne zusätzliche Capabilities
- AdGuard-Administration ohne öffentlichen Port
- Automatisierte Dependency-, Container-, Secret- und Static-Analysis-Scans in der CI
HTTPS
RootGuard terminiert bewusst kein eigenes TLS - das übernimmt ein etablierter Reverse Proxy davor. HTTPS über einen Reverse-Proxy deckt die zwei Voraussetzungen (Host-Header-Weiterleitung, X-Forwarded-Proto) und Beispielkonfigurationen für Caddy, Zoraxy, Nginx Proxy Manager und HAProxy ab.
Die folgenden Abschnitte gehen ins Detail: wem RootGuard wie weit vertraut, was ein kompromittierter Akteur jeweils erreichen kann, und welche Restrisiken bewusst offen sind.
Stand: 2026-08-08. Ergänzt die Architekturbeschreibung um eine explizite Betrachtung, wem RootGuard wie weit vertraut, was ein kompromittierter Akteur jeweils erreichen kann, welche Gegenmaßnahmen bereits greifen und welche Restrisiken bewusst offen oder nicht Ziel dieses Projekts sind.
Geltungsbereich¶
Betrachtet werden sechs Akteure/Grenzen: Docker-Socket-Inhaber, Browser, interne Netzwerke, Update-Supply-Chain, Backups und das AdGuard-Gateway. Außerhalb des Geltungsbereichs: Kompromittierung des Hosts selbst (physischer Zugriff, Kernel-Exploits, kompromittierte Basis-Images von Debian/Alpine/Docker) - RootGuard vertraut dem Host-Betriebssystem und der Docker-Engine, auf denen es läuft, so wie jede containerisierte Anwendung.
Akteure und Vertrauensgrenzen¶
1. Docker-Socket-Inhaber (Core, Updater)¶
Zugriff: rootguard-core und rootguard-updater haben /var/run/docker.sock gemountet. Wer diesen Socket kontrolliert, kontrolliert effektiv den Host - ein neuer privilegierter Container mit beliebigem Bind-Mount ist daraus trivial erreichbar.
Wenn kompromittiert: Vollständige Host-Kompromittierung. Das ist die mit Abstand größte Einzel-Vertrauensgrenze im System.
Bestehende Gegenmaßnahmen:
- Beide Images laufen aktuell als
root(siehe die Begründung direkt im jeweiligenDockerfile) - kein zusätzlicher Privilege-Escalation-Schritt nötig, aber auch keine Reduktion der Angriffsfläche innerhalb des Containers selbst. - Core spricht nur mit einem fest definierten, kontrollierten Satz von Images, Volumes, Netzwerken und Befehlen; Browser-Anfragen können weder Image-Namen noch Compose-Argumente oder Container frei wählen. Ein kompromittierter Browser oder eine kompromittierte WebApp kann Core also nicht direkt zu beliebigen Docker-Befehlen bewegen - nur zu den engen, typisierten Operationen, die die Core-API tatsächlich anbietet.
- Der Updater kennt ausschließlich
coreundwebappals Austauschziele, zieht nur konfigurierte Ziel-Images und bleibt vom Host aus nicht erreichbar. - Manuelle Docker-Bereinigung akzeptiert keine Ressourcennamen aus dem Browser: Core leitet Kandidaten ausschließlich aus seiner erfolgreichen Update-Historie oder dem festen Volume-Label
io.rootguard.cleanup=trueab, prüft die Nutzung vor Vorschau und Ausführung erneut und ruft keine globalen Prune-Befehle auf. - Digest-gepinnte Core-/WebApp-Releases durchlaufen vor der Aktivierung eine Cosign-/SLSA-Provenienzprüfung gegen den erwarteten GitHub-Workflow-Unterzeichner (siehe Abschnitt 4).
Bekannte Restrisiken / offen:
- Ein Bug oder eine Schwachstelle innerhalb von Core oder Updater, der einem Angreifer erlaubt, eigene Docker-API-Aufrufe statt der vorgesehenen engen Operationen abzusetzen, würde direkt zur Host-Kompromittierung führen - es gibt keine zweite Verteidigungslinie zwischen „Code-Fehler in Core" und „voller Docker-Zugriff".
- Konkreter geplanter Härtungsschritt: ein dediziertes
docker-socket-proxy-Sidecar, das den Socket selbst hält und nur eine eng allowlistete Teilmenge der Docker-API durchlässt, während Core und Updater selbst unprivilegiert laufen. Noch nicht umgesetzt.
2. Browser / authentifizierter Nutzer¶
Zugriff: HttpOnly-/SameSite=Strict-Session gegen die WebApp; darüber alle geführten und Experten-Funktionen der Oberfläche.
Wenn kompromittiert (gestohlene Session, XSS, o. ä.): Voller Administrator-Zugriff auf alle RootGuard-Funktionen - Konfigurationsänderung, Updates, AdGuard-Verwaltung.
Bestehende Gegenmaßnahmen:
- PBKDF2-SHA256 mit 600.000 Iterationen für das Admin-Passwort, konstante Vergleichszeit, HttpOnly-/SameSite=Strict-Cookies mit
Securesobald HTTPS aktiv ist. - Mutierende Anfragen müssen vom gleichen Origin stammen (Same-Origin-Write-Check) - klassisches CSRF über ein fremdes Origin greift damit nicht.
- Der Experteneditor für Unbound ist auf eine einzelne Datei (
90-rootguard-custom.conf) begrenzt; Includes, Listener, Remote Control, Containerpfade und Trust-Anker bleiben gesperrt - selbst ein vollständig kompromittierter Browser-Zugang kann darüber nicht auf Host-Dateien oder beliebige Unbound-Direktiven zugreifen. - Der separate
ROOTGUARD_RECOVERY_TOKENfür Passwort-Reset gewährt für sich genommen weder Session noch Zugriff auf Core oder AdGuard. - Session-Inventar mit gezielter Revocation über "Aktive Sitzungen" im Kontomenü - eine gestohlene Session muss nicht mehr bis zum TTL-Ablauf gültig bleiben.
- Rate-Limiting auf Login und Passwort-Recovery: 5 Fehlversuche pro 5-Minuten-Fenster sperren weitere Versuche - auch ein danach korrektes Passwort wird während einer aktiven Sperre abgelehnt, damit eine Sperre nicht durch schlichtes Weiterprobieren umgangen werden kann.
- Ein begrenztes, persistiertes Audit-Log (max. 500 Einträge) zeichnet Login-Erfolg/-Fehlschlag, Rate-Limiting, Logout, Passwort-Recovery und Session-Revocation auf - sichtbar im selben "Aktive Sitzungen"-Panel.
- Dasselbe Rate-Limit-/Audit-Prinzip deckt auch destruktive Aktionen außerhalb der Authentifizierung ab: ein gemeinsames, sitzungsbasiertes Sliding-Window-Budget (30 Anfragen / 5 Minuten über alle geschützten Routen hinweg, kein separates Budget pro Route) begrenzt Unbound-Aktivierung/-Restore/-Custom-Config/-Import/-Diagnose-Logging, Service-Start/Stop/Restart und -Updates, Backup-Einstellungen/-Export/-Restore, manuelles Cleanup, Control-Plane-Update-Installation, Installations-Deploy sowie AdGuard-Bootstrap und Filter-Toggle. Alle betroffenen Routen sind reine Proxies der WebApp zu Core, wodurch der Schutz am einzigen browserseitigen Einstiegspunkt greift, ohne Core selbst ändern zu müssen.
Bekannte Restrisiken / offen:
- Das geteilte Budget schützt vor massenhaftem Missbrauch einer einzelnen (z. B. kompromittierten) Session, nicht vor einem einzelnen gezielten destruktiven Aufruf durch einen legitim authentifizierten Nutzer - das ist erwartetes Verhalten, kein Bug: Auth-gestützte Autorisierung bleibt die eigentliche Zugriffskontrolle, das Rate-Limit ist eine zusätzliche Schadensbegrenzung.
3. Interne Netzwerke (control, edge, egress, DNS-Netz)¶
Zugriff: Vier Docker-Netzwerke trennen Zuständigkeiten: edge (WebApp-Host-Port), control (WebApp↔Core↔Updater, internal: true - keine Internet-Route), egress (echter Internetzugang, aber nur rootguard-attestation-proxy hängt hier), und das interne DNS-Netz (AdGuard↔Unbound). Nur WebApp und der DNS-Port erhalten Host-Ports; AdGuards native Administration bleibt ausschließlich intern erreichbar.
Wenn kompromittiert (z. B. ein anderer Container im selben Docker-Host/-Netzwerk): Zugriff auf interne, eigentlich nicht öffentlich gedachte Schnittstellen (AdGuard-Admin-API, Core-Bearer-Token-API).
Bestehende Gegenmaßnahmen:
- Netzsegmentierung wie oben; Core erreicht Unbound nur über eine fest definierte interne Adresse (
172.29.53.2:5335), kein öffentlicher Fallback. - Core-API ist bearer-token-geschützt (
ROOTGUARD_API_TOKEN), nicht nur netzwerk-isoliert. controls eigene Internet-Isolation bleibt vollständig, bis auf einen einzigen schmalen, auditierbaren Pfad:rootguard-attestation-proxy, ein reiner CONNECT-Forward-Proxy mit fest einprogrammierter 4-Host-Allowlist (ghcr.io,pkg-containers.githubusercontent.com,tuf-repo-cdn.sigstore.dev- genau das, was Cosigns eigene Attestation-Prüfung braucht, empirisch bestätigt, nicht mehr - sowieapi.github.comfür Cores GitHub-Releases-Live-Erkennung). Das ist Defense-in-Depth, keine Authentifizierungsgrenze - Core und der Updater, die einzigen zwei möglichen Aufrufer, halten bereits den Docker-Socket und laufen als root, besitzen also schon volle Host-Privilegien; der Zweck ist,controlnachweislich internet-isoliert zu halten und dabei den einen legitimen Egress-Pfad explizit zu machen, statt Internetzugang pauschal wieder zu öffnen.
Bekannte Restrisiken / offen:
- Wer bereits im selben Docker-Host beliebige Container starten kann, kann sich in der Regel auch in
control/das interne DNS-Netz hängen (ohne zusätzliche Docker-Netzwerkrichtlinien/Firewalling auf Host-Ebene) - die Netzsegmentierung schützt vor externen Angreifern und vor anderen, nicht-privilegierten Workloads auf demselben Host, nicht vor einem Angreifer, der bereits Docker-Zugriff auf demselben Host hat. Das deckt sich mit Akteur 1: Wer den Docker-Socket kontrolliert, kontrolliert auch die Netzwerke.
4. Update-Supply-Chain¶
Zugriff: GHCR-Images für alle sechs Komponenten; der Updater-Helper zieht und aktiviert neue Core-/WebApp-/AdGuard-/Unbound-Images.
Wenn kompromittiert (z. B. gestohlene GHCR-Publish-Credentials, kompromittierter CI-Runner): Ein bösartiges Image könnte als legitimes RootGuard-Release verteilt und von bestehenden Installationen automatisch übernommen werden - letztlich gleichbedeutend mit Akteur 1 (Host-Kompromittierung), nur über den Update-Pfad statt direkt.
Bestehende Gegenmaßnahmen:
- Digest-gepinnte Core-/WebApp-/Updater-/Unbound-/Blockpage-Releases werden alle vor der Aktivierung per Cosign gegen die signierte SLSA-Provenienz geprüft: erwarteter GitHub-Repository- und Workflow-Unterzeichner, erwarteter GitHub-Actions-OIDC-Aussteller, Prüfung der Sigstore-Transparenzdaten. Der eingebettete Cosign-Verifier selbst ist per Digest gepinnt - keine bewegliche Abhängigkeit an dieser Stelle.
- Lokale Builds, veränderliche Tags (
:latesto. ä.) und Fremdimages erhalten explizit keine RootGuard-Vertrauensfreigabe. - Ein Update-Fehlschlag (Healthcheck nach Austausch) pinnt automatisch die vorherige Image-ID zurück und prüft erneut.
- CI-seitig:
trivyprüft alle sechs Komponenten-Images/-Dockerfiles auf bekannte Schwachstellen und Fehlkonfigurationen,govulncheckundstaticchecklaufen gegen jedes Go-Modul,gitleaksgegen die gesamte Git-Historie - reduziert das Risiko, dass eine bekannte Schwachstelle oder ein versehentlich committetes Secret unbemerkt in ein veröffentlichtes Release gelangt.
Bekannte Restrisiken / offen:
- AdGuard Home (ein Fremd-Image) ist die eine Komponente, die keine Cosign-Provenienzprüfung durchläuft - Vertrauen basiert hier auf Digest-Pinning und der Upstream-Signatur, nicht auf einer RootGuard-eigenen Signaturkette.
rootguard-attestation-proxyselbst ist seit 2026-09-03 ins Self-Update-Management aufgenommen und wird über dieselbe Cosign-Policy geprüft wie jede andere RootGuard-eigene Komponente; die Verifizierung eines neuen Proxy-Kandidaten-Images läuft über die aktuell laufende Proxy-Instanz, der Swap passiert erst danach - keine Bootstrapping-Lücke. - Kein SBOM/keine Provenance für jedes Release war früher eine offene Lücke - inzwischen ausgeliefert.
- Kein einheitliches Image-Signing über Cosign hinaus für alle sechs selbst-update-fähigen Komponenten war ebenfalls eine offene Lücke - inzwischen ausgeliefert.
5. Backups¶
Zugriff: Persistente RootGuard-Volumes (Konfigurationshistorie, Sessions, AdGuard-Zugangsdaten, Installationszustand) und deren Sicherungen vor jedem Update/Austausch.
Wenn kompromittiert (Zugriff auf ein Backup/Volume-Snapshot): Offenlegung aller darin enthaltenen Zugangsdaten und Konfiguration - AdGuard-Admin-Zugangsdaten liegen mit Besitzerrechten im persistenten Volume, Sessions liegen serverseitig im Session-Volume.
Bestehende Gegenmaßnahmen:
- Backups/Snapshots entstehen ausschließlich serverseitig vor einem kontrollierten Austausch, nicht browserseitig abrufbar.
- Interne Update-Backups sind pro Dienst auf konfigurierbare 2-50 Wiederherstellungspunkte begrenzt (Standard 5); Speichernutzung und nicht erkannte Daten bleiben auf der Backups-Seite sichtbar.
- Automatische Bereinigung akzeptiert nur kanonische Zeitstempel-/Dienstpfade mit einem Manifest, das zum erlaubten Dienst und Container passt. Unbekannte Daten und Symlinks werden nicht gelöscht.
- Passwort-Hashes sind PBKDF2-SHA256-gesalzen, nicht im Klartext.
- Portable Vollbackups werden vor dem Download interoperabel mit age-v1 und einer scrypt-abgeleiteten Passwortidentität authentifiziert verschlüsselt. Ein versioniertes Manifest enthält SHA-256-Prüfsummen; Sitzungen und externe
.env-Geheimnisse sind nicht Teil des Exports. Fest verdrahtete Quellen, Symlink-Ablehnung und privates, immer entferntes Klartext-Staging begrenzen Pfad- und Restdatenrisiken. - Der geführte Restore prüft vor jeder Änderung Schema, Pflichtdateien, erlaubte Pfade/Typen, exaktes Manifest, Größen und SHA-256 sowie harte Upload-/Entpack-/Dateigrenzen. Apply validiert erneut, verlangt Bestätigung und scheitert geschlossen, wenn Installation oder verwaltete Docker-Ressourcen nicht sauber sind.
Bekannte Restrisiken / offen:
- Das Exportpasswort wird nicht gespeichert und muss für jede Vorschau und Wiederherstellung erneut eingegeben werden. Über reines HTTP ist das fertige Archiv verschlüsselt, das Passwort auf dem Weg vom Browser zur lokalen WebApp jedoch nicht transportgeschützt; die Oberfläche warnt sichtbar - siehe HTTPS über einen Reverse-Proxy für den unterstützten HTTPS-Betrieb.
- Live-Daten können sich während einzelner Dateikopien verändern. Updates sind zwar ausgeschlossen, ein transaktionsartiger Dienst-Snapshot und seine Restore-Verifikation bleiben jedoch eigene offene Punkte.
6. AdGuard-Gateway¶
Zugriff: RootGuard proxied die native AdGuard-Home-Oberfläche unter dem festen Pfad /adguard-ui/; Core setzt dabei die internen AdGuard-Zugangsdaten ein.
Wenn kompromittiert (Schwachstelle in AdGuard Home selbst oder im Proxy-Pfad): Zugriff auf AdGuards Filterregeln, DNS-Anfrage-Logs (soweit aktiviert) und die AdGuard-Konfiguration - nicht jedoch auf Core, Unbound oder den Docker-Socket, da AdGuard selbst keinen dieser Zugriffe besitzt.
Bestehende Gegenmaßnahmen:
- AdGuards native Administration ist ausschließlich intern erreichbar, nie über einen Host-Port.
- Nur der feste, authentifizierte Proxy-Pfad existiert; frei wählbare Ziele und ein öffentlicher Administrationsport sind ausgeschlossen.
- Der Reverse-Proxy setzt client-seitig gesetzte
X-Forwarded-*-Header vor dem Weiterreichen zurück und ersetzt sie serverseitig - schließt einen Header-Spoofing-Pfad, der zuvor offen war. - Mutierende Anfragen über den Proxy-Pfad müssen ebenfalls vom gleichen Origin stammen.
Bekannte Restrisiken / offen:
- AdGuard Home selbst liegt außerhalb der RootGuard-Codebasis - eine Schwachstelle in AdGuard Home wirkt sich über diesen Pfad direkt auf RootGuard-Installationen aus. Mitigiert nur durch Digest-Pinning und zeitnahes Nachziehen von AdGuard-Releases, nicht durch eine RootGuard-eigene zusätzliche Schutzschicht.
Bewusst nicht Ziel (Non-Goals)¶
- Böswilliger Administrator: Wer legitime Admin-Zugangsdaten besitzt, ist per Definition vertrauenswürdig - RootGuard schützt nicht vor einem autorisierten, aber böswillig handelnden Betreiber.
- Physischer Host-Zugriff: Wer physischen oder Hypervisor-Zugriff auf den Host hat, kann jede containerisierte Anwendung umgehen.
- Kompromittierte Basis-Images/Registries selbst (Docker Hub, GHCR, Debian/Alpine-Paketquellen) - RootGuard verifiziert, was es referenziert (Digest-Pinning, Cosign wo verfügbar), vertraut aber letztlich denselben Wurzeln wie jede andere containerisierte Anwendung.
- Mehrbenutzer-/Rollenmodell und externe Identity-Provider - nur bei echtem Bedarf nach 1.0.
- HTTPS/TLS-Terminierung durch RootGuard selbst - bewusste Scope-Entscheidung: dokumentierter Betrieb hinter einem etablierten Reverse-Proxy (Caddy, Zoraxy, Nginx Proxy Manager, HAProxy) statt einer eigenen TLS-Implementierung, siehe HTTPS über einen Reverse-Proxy.
Verweise¶
- Architekturbeschreibung - detaillierte Beschreibung der hier referenzierten Mechanismen.
- SECURITY.md - Meldeweg für Sicherheitslücken.