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.
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 BehobenDatei: tools/secure_browser_manager/browser_container_builder.py
— validate_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.
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.py
— build_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.
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.py
— build_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.
_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