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

Sicherheits-Audit — Juli 2026 — Runde 5

Code-Security-Review
NetMon Roots v1.1.16

Fünftes automatisiertes Security-Review auf Basis von Claude Sonnet 5. Schwerpunkt dieser Runde: das neue Secure Browser Manager-Feature — sowohl das eigenständige Prototyp-Werkzeug tools/secure_browser_manager/browser_container_builder.py als auch das integrierte App-Modul netmon/modules/secure_browser/ (manager.py, service.py, ui.py) — dazu die Härtung von agent/runner.py und agent/tools.py, die WireGuard-Preshared-Key-Ergänzung in vpn_controller.py sowie die cgroup-basierte Selbst-Erkennung in der Firewall-Transparenz (app.py, network_helpers.py). Anders als in Runde 4 wurden diesmal 3 echte Findings bestätigt — alle drei wurden noch in dieser Runde gepatcht und per Exploit-Reproduktion sowie automatisierten Tests verifiziert.

← Runde 4 (1 Kandidat — False Positive, 0 Findings)
PDF herunterladen Kandidaten Hardening

0 offene Findings — 3 Findings gefunden und noch in dieser Runde behoben

Anders als in Runde 4 identifizierte diese Runde 3 bestätigte Schwachstellen im brandneuen Secure Browser Manager-Feature: fehlende Namensvalidierung mit Pfad-Escape-Potenzial (K1), fehlendes Escaping beim Erzeugen von Firefox-user.js-Präferenzen (K2) und fehlende Zeilenumbruch-Bereinigung in .desktop-Launcher-Feldern (K3). Alle drei wurden noch in derselben Sitzung gepatcht, per Exploit-Reproduktionsskript verifiziert und gegen die bestehenden 121 automatisierten Tests (54 + 67) regressionsgetestet — alle bestanden. Keine der drei Lücken war remote oder ohne lokale Mitwirkung des Nutzers (Namenseingabe bzw. Import einer manipulierten containers.json) ausnutzbar. Der Codestand v1.1.16 enthält nach den Fixes keine offenen ausnutzbaren Sicherheitslücken.

Datum
05. Juli 2026
Analysierte Version
v1.1.16 — Runde 5
Geänderte/neue Dateien
~13
Sicherheitsfokus-Module
12
Offene Findings
0
Bewertete Kandidaten
3 (alle behoben)
Konfidenz-Schwelle
7 / 10
Analysewerkzeug
Claude Sonnet 5

Umfang & Methodik

Schwerpunkt dieser Runde war ausschließlich das neue Secure Browser Manager- Feature (Commit 9baa6284 + aktueller Arbeitsstand), da alle übrigen Module aus Runde 4 unverändert geblieben sind. Zusätzlich wurden alle kleineren Diffs seit Runde 4 (agent/runner.py, agent/tools.py, vpn_controller.py, network_helpers.py, app.py, log_tab.py, tools/netmon.py) sowie Commit 9d660ae3 (Runtime-Log-Persistenz) geprüft.

Phase 1 — Reconnaissance

Vollständiges Lesen von netmon/modules/secure_browser/*.py (manager/service/ui/models), secure_browser_tab.py und browser_container_builder.py; Abgleich gegen die im README dokumentierten Sicherheitsgarantien (Dry-Run-Default, kein sudo, kein Schreiben nach ~/.mozilla/firefox).

Phase 2 — Kandidaten-Analyse

Dedizierter Sub-Agent verfolgte Datenflussketten von Nutzereingaben (Containername, Homepage-URL, containers.json) bis zu Dateisystem-Operationen (mkdir, copytree, .desktop-Erzeugung). 4 Kandidaten mit Konfidenz 7–8/10 identifiziert, davon 2 mit identischer Ursache zusammengefasst.

Phase 3 — Fix & Exploit-Verifikation

Alle 3 bestandenen Kandidaten direkt am Quelltext nachvollzogen (kein False Positive), Patches implementiert und mit einem Reproduktionsskript verifiziert: Pfad-Escape-Name wird jetzt abgelehnt, injizierte Anführungszeichen in user.js werden korrekt escaped.

Phase 4 — Regressionscheck

Bestehende Testsuiten erneut ausgeführt: tests/test_browser_container_builder.py (54 Tests) und tests/test_secure_browser_module.py (67 Tests) — alle 121 weiterhin grün nach den Fixes.

Sicherheitsfokus-Module (Runde 5)

Datei / Modul Fokus Ergebnis
netmon/modules/secure_browser/manager.py Containername-/Pfad-Ableitung, Dry-Run-Vorschau, Apply-Freigabe mit Bestätigungscode K1 → behoben
netmon/modules/secure_browser/service.py Subprocess-Delegation an browser_container_builder.py (List-Form, shell=False), Backup-/Log-Rendering Kein Finding
netmon/modules/secure_browser/ui.py + secure_browser_tab.py Tkinter-Formulare, Eingabe-Weiterleitung an manager.py Kein Finding
tools/secure_browser_manager/browser_container_builder.py Containername-Validierung, Zielpfad-Aufbau, user.js- Generierung, .desktop-Launcher-Erzeugung K1 + K2 + K3 → behoben
tools/secure_browser_manager/containers.example.json Konfig-Schema (name, path, homepage_url …) Kein Finding
agent/runner.py python/cat aus Allowlist entfernt, git-Subcommand-Allowlist, Repo-Confinement für Pfadargumente Kein Finding (Härtung)
agent/tools.py _ensure_repo_path()-Guard für list_files/read_file Kein Finding (Härtung)
netmon/features/vpn_controller.py Neues Preshared-Key-Feld, Validierung via _validate_wireguard_key_value() (Base64, strikt) Kein Finding
netmon/app.py + netmon/services/network_helpers.py cgroup-basierte Selbst-Erkennung eigener Listener in der Firewall-Transparenz (ss -lntupeH) Kein Finding
netmon/ui/tabs/log_tab.py Checkbox „Log automatisch leeren“, nutzt bestehende Einstellung auto_log_clear Kein Finding
tools/netmon.py sys.path-Bereinigung vor Neueintrag von NETMON_REPO_ROOT (verhindert veraltete/doppelte Einträge) Kein Finding (Härtung)
netmon/app.py (Commit 9d660ae3) Runtime-Log-Persistenz — Pfade ausschließlich aus internem netmon_state_dir abgeleitet, keine Nutzereingabe Kein Finding

Außerhalb des Scopes: mehrere unversionierte Dateikopien unter tools/ (z. B. netmon_ops.py, netmon_network_service.py) sind byteidentische bzw. triviale Duplikate der bereits geprüften Module in netmon/ und nicht Teil der ausgelieferten Anwendung — keine eigenständige Analyse erforderlich.

Bestätigte Findings (alle in dieser Runde behoben)

Ein dedizierter Sub-Agent analysierte das neue Secure Browser Manager-Feature vollständig (Quelltext, nicht nur Diff). Alle drei Kandidaten wurden am Code bestätigt (kein False Positive) und noch in dieser Runde gepatcht. Ein Reproduktionsskript belegt sowohl die ursprüngliche Ausnutzbarkeit als auch die Wirksamkeit des jeweiligen Fixes.

K1 — Fehlende Namensvalidierung: Pfad-Escape beim Anlegen von Browser-Containern

Medium Behoben

Datei: tools/secure_browser_manager/browser_container_builder.pyvalidate_config_payload() (Zielverzeichnis + Menu-Datei); betrifft transitiv auch netmon/modules/secure_browser/manager.py, das Container-Erstellung per Subprocess an genau dieses Skript delegiert.

Das Feld name eines Container-Eintrags (aus containers.json oder aus dem Namensfeld im NetMon-UI-Dialog) wurde nur auf „nicht leer“ geprüft, aber nicht auf ein sicheres Zeichenset. Sowohl das Zielverzeichnis (base_dir / name) als auch der Dateiname des generierten .desktop-Menüeintrags (get_menu_filename()) wurden direkt aus diesem String abgeleitet — ohne Zeichensatzprüfung und ohne Confinement-Check gegen das Basisverzeichnis.

# vorher:
name = entry.get("name")
if not isinstance(name, str) or not name.strip():
    raise ValueError("Each container entry requires a non-empty 'name'.")
# ... path = base_dir / name  -> "../../../../.config/autostart/x" entkommt base_dir

Exploit-Szenario: Ein Container-Name wie ../../../../.config/autostart/update-check — per Copy&Paste in das Namensfeld oder über eine geteilte/importierte containers.json — verschiebt den erzeugten .desktop-Eintrag aus ~/.local/share/applications in ~/.config/autostart, wo er beim nächsten Login automatisch ausgeführt wird (Persistenzmechanismus). Gleiches Muster erlaubt beliebige Verzeichnisanlage/-kopie außerhalb des vorgesehenen Container-Basisverzeichnisses.

Fix: CONTAINER_NAME_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+$") in validate_config_payload() ergänzt — Namen mit / oder .. werden mit klarer Fehlermeldung abgelehnt, bevor irgendein Pfad daraus gebildet wird. Da sowohl das eigenständige CLI-Tool als auch das NetMon-UI-Modul Container-Erstellung über denselben load_config()-Pfad abwickeln, schließt ein einziger Fixpunkt beide Einstiegspfade. Mit Reproduktionsskript verifiziert: Escape-Name wird jetzt abgelehnt, ein regulärer Name wie firefox-GitHub funktioniert unverändert. Alle 54 bestehenden Tests weiterhin grün.

K2 — Firefox-user.js-Präferenz-Injection via Homepage-/Neue-Tab-URL

Medium Behoben

Datei: tools/secure_browser_manager/browser_container_builder.pybuild_user_js_content()

homepage_url und new_tab_url wurden per f-string direkt in user_pref("...", "{value}");-Zeilen eingesetzt, ohne Anführungszeichen zu escapen. Firefox interpretiert user.js als JS-ähnliche Anweisungsfolge — ein Wert mit eingebettetem " beendet den String vorzeitig und hängt beliebige weitere user_pref(...)-Aufrufe an.

# Beispielwert fuer homepage_url:
https://example.com");user_pref("network.proxy.type",1);//
# ergab vor dem Fix:
user_pref("browser.startup.homepage", "https://example.com");user_pref("network.proxy.type",1);//");

Auswirkung: Da diese Präferenzdatei genau die Isolations- und Telemetrie-Härtung setzt, die den „Secure“-Anspruch des Containers ausmacht (u. a. dom.security.https_only_mode, network.cookie.cookieBehavior), hätte eine manipulierte URL — z. B. aus einer geteilten Konfiguration — genau diese Schutzeinstellungen des neu erzeugten Containers untergraben können.

Fix: Beide Werte werden jetzt mit json.dumps() kodiert (user_pref("browser.startup.homepage", {json.dumps(homepage_url)});), was Anführungszeichen, Backslashes und Steuerzeichen korrekt escaped. Mit Reproduktionsskript verifiziert: derselbe Angriffswert erzeugt jetzt eine einzelne, harmlose user_pref-Zeile ohne injizierte Folgeanweisung.

K3 — Fehlende Zeilenumbruch-Bereinigung in .desktop-Launcher-Feldern

Low Behoben

Datei: tools/secure_browser_manager/browser_container_builder.pybuild_launcher_content(), build_existing_launcher_content(), build_menu_launcher_content()

Name=/Comment=-Zeilen in generierten .desktop-Dateien wurden direkt aus spec.name bzw. menu_name gebildet, ohne eingebettete Zeilenumbrüche zu entfernen. Ein Wert mit \n (z. B. via importierter Konfig oder Zwischenablage-Einfügen in ein Tk-Eingabefeld) hätte zusätzliche Desktop-Entry-Schlüssel einschleusen können; da manche Parser bei doppelten Schlüsseln den zuletzt gelesenen Wert übernehmen, ist ein zweiter, injizierter Exec=-Eintrag implementierungsabhängig nicht auszuschließen.

Fix: Neue Hilfsfunktion _desktop_field() entfernt \r/\n aus name/menu_name, bevor sie in Name=/Comment= eingesetzt werden — an allen drei Launcher-Erzeugungsstellen angewendet.

Sicherheitsverbesserungen seit Runde 4

Zusätzlich zu den drei in dieser Runde behobenen Findings (K1–K3, siehe oben) wurden folgende Härtungsmaßnahmen seit Runde 4 umgesetzt.

🔒

Agent-Runner-Allowlist verkleinert — agent/runner.py

python und cat wurden aus ALLOWED_BASE_COMMANDS entfernt — beide erlaubten dem Coding-Agenten zuvor beliebigen Code auszuführen bzw. beliebige Dateien zu lesen, sobald ein Pfadargument zulässig war. Neu: ALLOWED_GIT_SUBCOMMANDS = {"status","diff","log","show"} begrenzt git auf reine Lesebefehle, und _validate_repo_confined_arguments() erzwingt für black/ruff/pytest/ls, dass alle Pfadargumente nach resolve() innerhalb von REPO_ROOT liegen.

Repo-Confinement für Datei-Tools — agent/tools.py

Neue Hilfsfunktion _ensure_repo_path() prüft in list_files() und read_file(), dass der aufgelöste Pfad via relative_to(REPO_ROOT) innerhalb des Repositories bleibt, statt wie zuvor jeden aufgelösten Pfad kommentarlos zu akzeptieren.

sys.path-Bereinigung — tools/netmon.py

Statt nur zu prüfen, ob NETMON_REPO_ROOT irgendwo in sys.path vorkommt, werden jetzt zuerst alle Einträge entfernt, die auf denselben Pfad auflösen, bevor der Pfad an Position 0 neu eingefügt wird. Verhindert, dass ein anderweitig bereits vorhandener (z. B. relativer oder veralteter) Eintrag Vorrang vor dem echten Repository-Root bekommt und dadurch Module aus einem falschen Verzeichnis importiert werden.

Transparenz: Eigene Listener in der Firewall-Ansicht erkennbar

app.py + network_helpers.py: ss -lntupeH (mit cgroup-Ausgabe) ergänzt bestehende ss-Aufrufe als erste Variante; _is_netmon_listener() und _is_netmon_app_scope() erkennen anhand von Prozessname, PID und systemd-Scope (app-gnome-netmon-*.scope), ob ein offener Port zu NetMon selbst gehört, und kennzeichnen ihn in der Firewall-Transparenz-Tabelle entsprechend — erleichtert Nutzern die Unterscheidung zwischen eigenen und fremden Listenern.

Versions-Bump auf v1.1.16

netmon/config/constants.py: NETMON_APP_VERSION von 1.1.15 auf 1.1.16 angehoben, passend zum in dieser Runde veröffentlichten Codestand (site/latest-version.json bereits synchronisiert).

Hardening-Empfehlungen

Sämtliche seit Runde 1 dokumentierten Defense-in-Depth-Empfehlungen bleiben im Codestand v1.1.16 unverändert umgesetzt (per Quelltext-Check erneut stichprobenartig geprüft). Eine neue, nicht-dringende Empfehlung kommt in dieser Runde hinzu.

IP-Validierung für WireGuard-Endpoints — vpn_controller.py:2479

parse_vpn_endpoint() verwendet ipaddress.ip_address(host) zur strikten Host-Validierung. Nicht-IP-Hostnamen werden übersprungen und via _warn_non_ip_endpoint_once() protokolliert. Der Host-Teil erreicht UFW-Kommandos nur als validiertes endpoint_ip.compressed. (Runde 1, Punkt 2 — geschlossen)

Schema-Validierung homepage-URLs im Radio-Modul — radio_controller.py:106+315

ALLOWED_RADIO_HOMEPAGE_SCHEMES = {"http", "https"} (Zeile 106). Die Funktion _normalize_station_homepage_url() (Zeilen 315–326) validiert jede Homepage-URL via urllib.parse.urlparse() gegen diese Allowlist und wird in open_selected_station_homepage() (Zeile 5231) aufgerufen — bevor webbrowser.open() die URL erhält. (Runde 2, Punkt 4 — geschlossen)

Shell-Quoting in Repository-Guard-Snippets — repository_guard_controller.py:799–802

get_repository_guard_quick_action_snippet() maskiert alle eingebetteten Werte mit shlex.quote(): quoted_uri, quoted_path, quoted_host und quoted_keyring_path. (Runde 1, Punkt 5 — geschlossen)

realpath() im Roots-Baseline-Guard — roots_baseline_guard_controller.py:123

_normalize_guard_path_pattern() verwendet os.path.realpath(os.path.expanduser(raw)) — Symlinks werden vollständig aufgelöst, bevor fnmatch-Vergleiche und Allowlist-Prüfungen greifen. (Runde 1, Punkt 6 — geschlossen)

Pfadprüfung in image_to_data_url()ai_online_service.py:35–39

resolved.relative_to(home_dir) stellt sicher, dass der Bildpfad innerhalb des Benutzer-Homeverzeichnisses liegt. Zusätzlich wird resolve(strict=True) verwendet (Symlinks aufgelöst, Existenz sichergestellt) und die Dateiendung gegen eine Allowlist erlaubter Bildformate geprüft. (Runde 1, Punkt 3 — geschlossen)

WireGuard-Import: Hook-Hinweis — vpn_controller.py:141+3229+3328+3363

_warn_wireguard_import_hooks() (Zeile 141) scannt jede ausgewählte .conf-Datei vor dem Import auf PreUp/PostUp/PreDown/PostDown-Direktiven und zeigt bei Fund einen expliziten Nutzerhinweis an (messagebox mit Option zum Abbruch). Die Funktion wird an drei Import-Einstiegspunkten aufgerufen (Zeilen 3229, 3328, 3363). (K1-FP Defense-in-Depth — geschlossen)

Offen für Runde 6: Client-seitige Namensvalidierung im Secure-Browser-Dialog

Die Durchsetzung von K1 (Namens-Charset) sitzt aktuell ausschließlich in browser_container_builder.py (Subprocess-Ebene) — funktional korrekt und ausreichend, aber ein ungültiger Name führt im NetMon-UI-Dialog derzeit erst nach dem Subprocess-Aufruf zu einer Fehlermeldung statt zu sofortigem Inline-Feedback im Formular. Empfehlung: dieselbe CONTAINER_NAME_PATTERN-Prüfung zusätzlich in manager.py::_resolve_create_inputs() spiegeln, rein für bessere UX — keine sicherheitskritische Lücke, da der Schreibpfad bereits fail-closed ist.

Geprüfte Sicherheitskategorien

Kategorie Geprüft Ergebnis
Command Injection (subprocess, shell=True) Ja Kein Finding
Path Traversal (Dateioperationen mit Nutzereingabe) Ja K1 → behoben (Secure Browser Manager)
Privilege Escalation (sudo mit unkontrollierten Argumenten) Ja Kein Finding
Argument Injection (list-form subprocess mit externen Daten) Ja Kein Finding
Unsichere Deserialisierung (pickle, yaml.load, eval) Ja Kein Finding
Eval/Exec mit Nutzereingabe Ja Kein Finding
Hardcoded Credentials / API-Keys Ja Kein Finding
Sensitive Data Exposure (PII in Logs) Ja Kein Finding
SSRF via urllib.request / externe URL-Weitergabe Ja Kein Finding
URL-Schema-Validierung an Media-Subprocessen Ja Kein Finding
Kryptographische Schwachstellen Ja Kein Finding
Symlink-Angriffe auf privilegierte Dateioperationen Ja Kein Finding
Mountpoint-Manipulation via Konfig-Datei Ja Kein Finding (Fix in v1.1.15)
WireGuard-Hook-Ausführung via Import Ja K1 R4 → FP (kein Boundary Crossing)
Desktop-Entry-Injection (.desktop Name=/Comment=) Ja K3 → behoben (Secure Browser Manager)
Firefox user.js/Preference-Injection Ja K2 → behoben (Secure Browser Manager)
Agent-Runner Command-/Pfad-Allowlist Ja Gehärtet (agent/runner.py, agent/tools.py)
PATH-Hijacking via shutil.which (R3-K1) Ja Geschlossen (R3)
Agent-CLI Pfad-Confinement (R3-K3) Ja Geschlossen (R3)

Auditor: Claude Sonnet 5 (Anthropic) — Dedizierter Sub-Agent-Review + manuelle Fix-Verifikation per Exploit-Reproduktionsskript
Projektverantwortlicher: Sven Fröhlich
Datum: 05. Juli 2026
Analysierter Codestand: 920cfc9b + Arbeitsstand (Secure Browser Manager, modified/untracked), ~13 Dateien — Runde 5
Methode: Statische Code-Analyse + manuelle Datenfluss-Verifikation + Regressionstests (121/121 automatisierte Tests bestanden nach den Fixes) — 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   •   Legal / Lizenz