NETMON ROOTS MONITOR. CONTROL. UNDERSTAND. BACK TO THE ROOTS.
DE

Sicherheits-Audit — Juli 2026 — Runde 6

Code-Security-Review
NetMon Roots v1.1.16

Sechstes automatisiertes Security-Review auf Basis von Claude Fable 5. Schwerpunkt dieser Runde: die seit Runde 5 hinzugekommene Favicon-Download-Netzwerkfunktion des Secure Browser Managers (fetch_url(), resolve_favicon_download(), extract_icon_links() in browser_container_builder.py) — die erste ausgehende Netzwerk-Schnittstelle des Features — sowie die Pfad-Confinement-Härtung des Coding-Agenten (agent/runner.py) und das damals noch vorhandene Dev-Container-GUI-/Docker-Profil (.devcontainer/gui-docker/, inzwischen entfernt). Anders als in Runde 5 wurden keine ausnutzbaren Schwachstellen bestätigt: vier Kandidaten wurden von dedizierten Validierungs-Sub-Agenten allesamt unterhalb der Konfidenzschwelle eingestuft (2–3 / 10). Drei davon wurden dennoch als Defense-in-Depth proaktiv gehärtet und mit neuen Regressionstests abgesichert.

← Runde 5 (3 Findings — alle behoben)
PDF herunterladen Kandidaten Hardening

0 ausnutzbare Findings — 3 Defense-in-Depth-Härtungen proaktiv umgesetzt

Diese Runde untersuchte gezielt die seit Runde 5 neu hinzugekommene Angriffsfläche — vor allem die erste ausgehende Netzwerkfunktion des Secure Browser Managers (Favicon-Download) sowie die Pfad-Confinement-Härtung des Coding-Agenten. Ein Analyse-Sub-Agent identifizierte vier Kandidaten; jeder wurde von einem eigenen, parallel laufenden Validierungs-Sub-Agenten geprüft und allesamt unterhalb der Konfidenzschwelle (≥ 8/10) eingestuft — kein konkret ausnutzbarer Angriffspfad über eine echte Vertrauensgrenze. Drei der vier Punkte wurden trotzdem als sinnvolle Härtung direkt umgesetzt (Scheme-Allowlist für Netzwerk-Fetches, Schliessen einer --flag=value-Lücke im Agent-Pfad-Confinement, explizite Ablehnung von ./.. als Containername) und mit 6 neuen Regressionstests abgesichert. Alle 149 relevanten automatisierten Tests bleiben nach den Fixes grün. Der Codestand v1.1.16 enthält keine offenen ausnutzbaren Sicherheitslücken.

Datum
07. Juli 2026
Analysierte Version
v1.1.16 — Runde 6
Neue Angriffsfläche
Favicon-Download (Netzwerk)
Bewertete Kandidaten
4 (alle < Schwelle)
Ausnutzbare Findings
0
Härtungen umgesetzt
3
Konfidenz-Schwelle
8 / 10
Analysewerkzeug
Claude Fable 5

Umfang & Methodik

Schwerpunkt dieser Runde war die seit dem Runde-5-Snapshot neu hinzugekommene bzw. veränderte Angriffsfläche im Arbeitsstand: die erstmals eingeführte Favicon-Download-Funktion des Secure Browser Managers (ausgehende HTTP-Requests aus Homepage-URL und aus dem HTML geparsten Icon-Links), die Pfad-Confinement-Logik des Coding-Agenten (_looks_like_path_argument() / _validate_repo_confined_arguments()) sowie die neu hinzugefügten Dev-Container-Profile. Die in Runde 5 behobenen Findings (K1–K3) wurden zusätzlich am aktuellen Code gegengeprüft — die Fixes sind unverändert wirksam (CONTAINER_NAME_PATTERN, json.dumps()-Escaping, _desktop_field()).

Phase 1 — Diff- & Reconnaissance

Vollständiger git diff gegen den letzten Commit plus Sichtung aller neuen unversionierten Dateien; gezieltes Lesen der neuen Netzwerkpfade in browser_container_builder.py und der Härtungslogik in agent/runner.py. Abgleich gegen die im README dokumentierten Sicherheitsgarantien.

Phase 2 — Kandidaten-Analyse

Ein dedizierter Analyse-Sub-Agent verfolgte Datenflussketten von Nutzer-/Konfig-Eingaben (Containername, homepage_url, Agent-Kommandozeile) bis zu sensiblen Operationen (urlopen, mkdir/copytree, subprocess.run). 4 Kandidaten identifiziert.

Phase 3 — False-Positive-Validierung

Für jeden Kandidaten lief ein eigener, paralleler Validierungs-Sub-Agent mit striktem FP-Filter-Regelwerk und Konfidenz-Scoring. Ergebnis: alle vier unter der Schwelle (2–3 / 10) — kein Angriffspfad über eine echte Vertrauensgrenze in einem Einzelnutzer-Desktop-Werkzeug.

Phase 4 — Härtung & Regressionscheck

3 der 4 Punkte als Defense-in-Depth umgesetzt und mit 6 neuen Regressionstests abgesichert. Bestehende Suiten erneut ausgeführt: test_runner.py, test_secure_browser_module.py und test_browser_container_builder.py — 149 Tests, alle grün.

Sicherheitsfokus (Runde 6)

Datei / Modul Fokus Ergebnis
browser_container_builder.pyfetch_url() Ausgehende HTTP(S)-Fetches ohne Scheme-Restriktion (file:// via Default-Opener erreichbar) K1 → gehärtet
browser_container_builder.pyresolve_favicon_download() / extract_icon_links() SSRF-Betrachtung: homepage_url und aus HTML geparste Icon-Links steuern Ziel-Host Kein Finding (Self-SSRF)
agent/runner.py_looks_like_path_argument() Pfad-Confinement für pytest/black/ruff/ls; --flag=value-Argumente wurden nicht geprüft K2 → gehärtet
browser_container_builder.py / manager.py — Namensvalidierung CONTAINER_NAME_PATTERN matcht weiterhin ./.. K3 → gehärtet
netmon/app.py_is_netmon_listener() Substring-Match "netmon" in process_name zur Listener-Zuordnung in der Firewall-Transparenz K4 → Empfehlung (offen)
.devcontainer/gui-docker/devcontainer.json Historisches offenes Entwickler-Profil mit docker.sock- und X11-Mount; inzwischen aus dem aktuellen Setup entfernt Historisch / entfernt
Runde-5-Fixes (K1–K3) Gegenprüfung, ob die Patches am aktuellen Code weiterhin greifen Weiterhin wirksam

Außerhalb des Scopes: die im Repository-Root liegende index.html ist eine fremde Drittanbieter-Seite (Rootkit-Hunter- Projekt) und gehört nicht zur ausgelieferten Anwendung — empfohlen wird deren Entfernung/Verschiebung als reine Aufräum-Maßnahme. Das frühere GUI-/Docker-Dev-Container-Profil mit Host-Docker-Socket-Mount war ein bewusst offenes Entwickler-Feature, wurde aber aus dem aktuellen Setup entfernt und ist kein Bestandteil der ausgelieferten App.

Bewertete Kandidaten (alle unterhalb der Konfidenzschwelle)

Vier Kandidaten wurden identifiziert und jeweils von einem eigenen Validierungs-Sub-Agenten gegengeprüft. Keiner erreichte die Konfidenzschwelle von 8/10 für ein bestätigtes, ausnutzbares Finding — im Wesentlichen, weil es sich um ein Einzelnutzer-Desktop-Werkzeug handelt, bei dem die vermeintliche „Angreifer“-Eingabe (z. B. selbst geschriebene containers.json oder lokal getippte Agent-Kommandos) vom selben Nutzer stammt, der das Werkzeug ausführt. Drei der vier Punkte wurden dennoch als sinnvolle Defense-in-Depth gehärtet.

K1 — Ausgehende Netzwerk-Fetches ohne Scheme-Allowlist (Favicon-Download)

Konfidenz 3/10 Gehärtet

Datei: tools/secure_browser_manager/browser_container_builder.pyfetch_url(), aufgerufen aus resolve_favicon_download() (Standard-Icon-Modus favicon).

fetch_url() reichte homepage_url (und daraus per urljoin() abgeleitete Icon-Links) ohne Scheme-Prüfung an urllib.request.urlopen(). Der Default-Opener von urllib behandelt neben http/https auch file:// — ein homepage_url wie file:///etc/passwd hätte damit theoretisch einen lokalen Dateizugriff ausgelöst, und ein beliebiger Host wäre als SSRF-Primitive erreichbar gewesen.

Warum unter der Schwelle: In diesem Einzelnutzer-Desktop- Werkzeug wird homepage_url ausschließlich vom selben Nutzer gesetzt, der auch --apply ausführt (lokale containers.json bzw. GUI-Eingabe). Es existiert kein Remote-/Mehrbenutzer-Eingabepfad und kein Import-aus-URL-Feature — also keine echte Vertrauensgrenze, die überschritten wird (Self-SSRF / selbstverschuldete Fehlkonfiguration).

Härtung umgesetzt: fetch_url() prüft jetzt das Scheme und lehnt alles ab, was nicht http/https ist — damit ist der file://-Pfad und jede andere Nicht-Web-Scheme geschlossen, bevor die URL den Opener erreicht. Neuer Regressionstest test_fetch_url_rejects_non_http_schemes (file://, ftp://, gopher://, absoluter Pfad) verifiziert die Ablehnung.

K2 — --flag=value-Lücke im Pfad-Confinement des Coding-Agenten

Konfidenz 3/10 Gehärtet

Datei: agent/runner.py_looks_like_path_argument() / _validate_repo_confined_arguments().

Die in Runde 5 eingeführte Repo-Confinement-Prüfung behandelte jedes Argument, das mit - beginnt, sofort als „kein Pfad“ und übersprang es. Flags der Form --junitxml=/pfad, --basetemp=/pfad oder --output-file=/pfad (bei pytest/ruff) trugen ihren Pfad also am Confinement-Check vorbei — ein Schreibziel außerhalb des Repos wäre nicht abgewiesen worden.

Warum unter der Schwelle: Der einzige Aufrufer (agent/cli.py) baut das Kommando aus sys.argv — es ist ein manuell vom Betreiber gestartetes lokales CLI, keine an untrusted Input angebundene autonome LLM-Tool-Schleife. Damit greift der Grundsatz „vom vertrauenswürdigen Betreiber übergebene CLI-Argumente sind kein Angreifer-Input“. Die Absicht des Confinement-Codes wird dennoch vervollständigt.

Härtung umgesetzt: _looks_like_path_argument() zerlegt jetzt --flag=value-Argumente und prüft den Wert-Teil; eine neue Hilfsfunktion _flag_value() sorgt dafür, dass auch _is_repo_confined_path() den reinen Pfad-Teil bewertet. Neue Tests (test_run_command_rejects_pytest_flag_value_outside_repo, ...ruff..., ...allows_flag_value_inside_repo) bestätigen, dass Pfade außerhalb des Repos abgewiesen und solche innerhalb weiterhin erlaubt werden.

K3 — . / .. passieren die Containername-Validierung

Konfidenz 2/10 Gehärtet

Datei: tools/secure_browser_manager/browser_container_builder.py (validate_config_payload()) und netmon/modules/secure_browser/manager.py (_resolve_create_inputs()).

Das Runde-5-Muster ^[A-Za-z0-9_.-]+$ erlaubt den Punkt und matcht damit auch die Literale . und ... base_dir / name zeigt für .. lexikalisch auf das übergeordnete Verzeichnis.

Warum unter der Schwelle: In der Praxis blockiert die bestehende mkdir(parents=True, exist_ok=False)-Semantik den Schreibvorgang — ein empirischer Test bestätigte, dass bei name=".." zuerst base_dir selbst angelegt wird und der finale mkdir dann zuverlässig mit FileExistsError abbricht, bevor copytree erreicht wird. Kein Schreibzugriff außerhalb von base_dir heute möglich.

Härtung umgesetzt: An beiden Validierungsstellen wird name in {".", ".."} jetzt explizit mit klarer Fehlermeldung abgelehnt — die Namensprüfung ist damit selbst fail-closed und hängt nicht mehr an der zufälligen mkdir-Semantik. Neue Tests test_validate_config_payload_rejects_dot_and_dotdot_names und ...rejects_path_traversal_name sichern das ab.

K4 — Loser Substring-Match bei der NetMon-Listener-Zuordnung

Konfidenz 3/10 Empfehlung (offen)

Datei: netmon/app.py_is_netmon_listener().

Die Firewall-Transparenz stuft einen Listener als „gehört zu NetMon selbst“ ein, u. a. per if "netmon" in process_name. Ein lokal laufender Prozess mit einem Namen wie netmon-helper würde damit als eigener Listener gelabelt.

Warum unter der Schwelle & bewusst nicht geändert: Die Klassifikation dient rein der Anzeige/Priorisierung in einem informativen Diagnose-Report — sie steuert keine ufw-Regel und blendet keine Zeile aus der Haupttabelle aus (Prozessname, PID, Port bleiben sichtbar). Ein Angreifer, der einen Prozess frei benennen kann, hat bereits lokale Codeausführung als der Nutzer; das Verbergen einer Zeile in einer Zusammenfassung ist keine relevante Eskalation. Ein Entfernen des Matches birgt zudem das Risiko, echte NetMon-Listener fälschlich als „fremd“ zu markieren (schlechtere UX). Daher bewusst als Empfehlung belassen.

Empfehlung (Runde 7): Die Zuordnung künftig nur noch über schwer fälschbare Signale stützen — pid == os.getpid() und den systemd-Scope-Check _is_netmon_app_scope() (app-gnome-netmon-*.scope) — und den reinen Substring-Match entfernen, sobald die Selbst-Erkennung der eigenen Listener über diese Pfade verlässlich abgedeckt ist.

Umgesetzte Härtungsmaßnahmen (Runde 6)

Auch ohne bestätigtes ausnutzbares Finding wurden die drei risikoreduzierenden Kandidaten direkt geschlossen — jeweils fail-closed und mit Regressionstest.

🔒

Scheme-Allowlist für Netzwerk-Fetches — browser_container_builder.py

fetch_url() akzeptiert nur noch http/https. Damit sind file:// (lokaler Dateizugriff via urllib- Default-Opener) und andere Nicht-Web-Schemes ausgeschlossen, bevor die URL urlopen() erreicht.

Vollständiges Pfad-Confinement inkl. --flag=valueagent/runner.py

_looks_like_path_argument() zerlegt jetzt --flag=value-Argumente und prüft den Wertteil; die neue Hilfsfunktion _flag_value() stellt sicher, dass auch _is_repo_confined_path() den reinen Pfad bewertet. Schreibziele wie --junitxml=/etc/passwd werden nun abgewiesen.

Fail-closed Containername-Validierung — browser_container_builder.py + manager.py

. und .. werden an beiden Validierungseinstiegen explizit abgelehnt — die Namensprüfung hängt nicht mehr an der zufälligen mkdir(exist_ok=False)-Semantik als letzter Schutzlinie.

🧪

6 neue Regressionstests — 149 / 149 grün

test_runner.py (+3: --flag=value innerhalb/ außerhalb Repo), test_browser_container_builder.py (+3: ./..-Namen, Pfad-Escape-Name, Nicht-HTTP- Schemes). Alle drei relevanten Suiten bestehen nach den Fixes vollständig.

Runde-5-Fixes — am aktuellen Code gegengeprüft

Die drei in Runde 5 behobenen Findings wurden nicht nur dem Dokument entnommen, sondern am aktuellen Quelltext verifiziert — alle Patches sind unverändert wirksam.

K1 (R5) — Namensvalidierung

CONTAINER_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+$") vorhanden in browser_container_builder.py:35 und manager.py:54, an beiden Einstiegen angewandt — in Runde 6 zusätzlich um die ./..-Ablehnung erweitert.

K2 (R5) — user.js-Escaping

homepage_url/new_tab_url werden weiterhin per json.dumps() kodiert (browser_container_builder.py:1312–1313) — Preference-Injection bleibt geschlossen.

K3 (R5) — .desktop-Feld-Bereinigung

_desktop_field() entfernt weiterhin \r/\n und wird an allen drei Launcher-Erzeugungsstellen verwendet (browser_container_builder.py:1463 ff.).

Geprüfte Sicherheitskategorien

Kategorie Geprüft Ergebnis
SSRF / ausgehende URL-Fetches (Host-/Scheme-Kontrolle) Ja K1 → gehärtet (Scheme-Allowlist)
Local File Read via file:// im URL-Opener Ja K1 → gehärtet
Path Traversal (Dateioperationen mit Nutzereingabe) Ja K3 → gehärtet (fail-closed)
Agent-Sandbox: Pfad-Confinement der Kommando-Allowlist Ja K2 → gehärtet (--flag=value)
Command Injection (subprocess, shell=True) Ja Kein Finding
Argument Injection (list-form subprocess mit externen Daten) Ja Kein Finding
Firefox user.js/Preference-Injection Ja Geschlossen (R5-K2)
Desktop-Entry-Injection (.desktop Name=/Comment=) Ja Geschlossen (R5-K3)
Unsichere Deserialisierung (pickle, yaml.load, eval) Ja Kein Finding
Privilege Escalation (historisches Dev-Container docker.sock-Mount) Ja Historisch / entfernt
Sicherheitskontroll-Umgehung (Listener-Zuordnung) Ja K4 → Empfehlung (offen)
Hardcoded Credentials / API-Keys Ja Kein Finding
Sensitive Data Exposure (PII in Logs) Ja Kein Finding

Auditor: Claude Fable 5 (Anthropic) — Analyse-Sub-Agent + vier parallele False-Positive-Validierungs-Sub-Agenten + manuelle Fix-Umsetzung und Regressionstests
Projektverantwortlicher: Sven Fröhlich
Datum: 07. Juli 2026
Analysierter Codestand: 9baa6284 + Arbeitsstand (modified/untracked), Fokus Favicon-Download & Agent-Pfad-Confinement — Runde 6
Methode: Statische Code-Analyse + manuelle Datenfluss-Verifikation + Konfidenz-gescorte FP-Validierung + Regressionstests (149/149 relevante Tests bestanden nach den Härtungen) — kein Laufzeit-Penetrationstest
Haftungsausschluss: Dieses Audit ersetzt keinen professionellen Penetrationstest durch eine zertifizierte Sicherheitsfirma. Die Analyse beschränkt sich auf den angegebenen Codestand. Nicht erfasst werden: Laufzeitverhalten, abhängige Systembibliotheken, Netzwerkkonfiguration und soziale Angriffsvektoren.

Zur Downloadseite   •   Aktuelles   •   Dokumentation   •   Runde 5