Sicherheits-Audit — September 2026 — Runde 12
Code- und Release-Security-Review
NetMon Roots v1.1.21
Geprüft wurden 96 Commits seit Runde 11, die umfangreiche
Auslagerung der Tab-Oberflächen, privilegierte Systempfade und die
Release-Lieferkette. Das Refactoring führt keine neue Shell-,
Netzwerk- oder Privileg-Senke ein. Das mittlere DNS-Finding, die
TAR-Linkbehandlung, der Versionsdrift und die veraltete Supportmatrix
wurden behoben. Der echte Minisign-Public-Key ist mit der ID
54BF5F1867BDD004 in der Security Policy und als
kanonische Repository-Datei gepinnt.
Findings Runde 12
NM-R12-01 — Paketinstallation ändert aktive DNS-Profile ohne Opt-in
postinst entfernt. Neue Installationen verändern
keine NetworkManager-Profile. Beim Upgrade stellt
restore_legacy_install_dns vorhandene Altzustände wieder
her und entfernt anschließend die alte Statusdatei.
NM-R12-02 — Firefox-TAR-Prüfung verwirft keine Links
NM-R12-03 — Signatur-Vertrauen startet von derselben Web-Origin
NETMON_EXPECTED_MINISIGN_PUBKEY
einen unabhängig bezogenen Key erzwingen. Der echte Public Key mit
ID 54BF5F1867BDD004 ist in SECURITY.md und
docs/security/netmonroots.minisign.pub gepinnt.
Abweichende Public Keys werden vom Release-Build abgelehnt.
NM-R12-04 — Versionsdrift im Release-Prozess
netmon/config/constants.py. Downloadseiten,
Dokumentation und Update-JSON stehen auf 1.1.21.
check_release_consistency.py bricht den Release bei
abweichenden sichtbaren Metadaten ab.
NM-R12-05 — Veraltete Supportmatrix
SECURITY.md weist jetzt 1.1.21 als einzige
unterstützte Release-Linie aus und trennt veröffentlichte Releases
von internen Entwicklungsständen.
Prüfung und Einordnung
Refactoring-Delta
96 Commits und 21 Produkt-, Build- und Testdateien. Keine neue Shell-, Netzwerk-, Datei- oder Privileg-Senke in den ausgelagerten UI-Modulen.
Statische Analyse
Ruff, Ruff-Security, Bandit 1.9.4 und manuelle Datenflussprüfung. Die ursprünglichen High-Heuristiken wurden einzeln validiert und anschließend technisch geklärt; der Bandit-Nachscan auf High-Schwelle ist ohne Treffer.
Regression und Laufzeit
2595 Tests bestanden, 2 übersprungen. Die gezielten R12-Sanierungsregressionen: 33 bestanden. Security-Auswahl: 651 bestanden, 1 übersprungen. GUI-Smoke, Black und Shellcheck bestanden.
Abhängigkeiten
pip-audit meldet für die aktuell aufgelösten
Anforderungen keine bekannten Schwachstellen. Die ungebundenen
Versionsangaben bleiben ein Reproduzierbarkeitsthema.
Der vollständige technische Bericht liegt im Repository unter
docs/SECURITY_AUDIT_2026_R12.md. Dieses Review ist eine
statische Code- und Release-Prozessprüfung und ersetzt keinen
unabhängigen Penetrationstest.
Vorrunde 11 — vollständiger Bericht
Sicherheits-Audit — Juli 2026 — Runde 11
Code-Security-Review
NetMon Roots v1.1.20
Elftes automatisiertes Security-Review auf Basis von
Claude Fable 5. Geprüft wurde der komplette Diff seit dem
Runde-10-Endstand — 215 Commits, davon der weit
überwiegende Teil mechanische Refactorings. Neu und
sicherheitsrelevant war vor allem das noch unveröffentlichte
UFW-sysctl-Override-Feature, das
/etc/ufw/sysctl.conf als root umschreibt, dazu ein neues
Laufzeitmess-Modul. Ergänzend lief eine
Ist-Stand-Vertiefung der Radio-Senken, die bisher nie
im Diff-Scope einer Runde lagen.
Diese Runde brachte kein Finding oberhalb der
Melde-Schwelle — von drei Kandidaten wurde einer
widerlegt, einer auf LOW herabgestuft. Umgesetzt wurden dennoch
acht Defense-in-Depth-Härtungen mit
26 Regressionstests.
Umfang & Methodik
Geprüft wurde der komplette Diff seit dem Runde-10-Endstand
(445279ac, 20. Juli 2026) bis zum aktuellen
Arbeitsstand: 215 Commits, 468 Dateien, rund
40.000 Zeilen — davon jedoch nur etwa
3.750 Zeilen Produktivcode in 22 Modulen;
der Rest sind Tests und Refactoring-Dokumentation. Schwerpunkte: das
neue, noch uncommittete UFW-sysctl-Override-Feature
(security_audit_controller.py), das neue
Laufzeitmess-Modul
(core/performance_monitor.py) samt seiner Einbindung in
den zentralen Kommandopfad core/command.py, sowie die
umfangreichen Refactorings in Radio-App und -Tab. Ergänzend
wurde der Scope bewusst erweitert: die scharfen Radio-Senken
(radio_controller.py, netmon_radio_core.py)
sind seit Runden unverändert und lagen daher nie in einem
Diff-Scope — sie wurden erstmals im Ist-Stand
geprüft. Die Härtungen der Runden 9 und 10 wurden
auf Regressionen gegengeprüft.
Phase 1 — Diff-Recon
Bestimmung des Rundenscopes gegen den Runde-10-Endstand, Trennung von Produktivcode, Tests und Dokumentation. Gegenprüfung der Vorrunden-Härtungen (xdg-open-Allowlist, Killswitch-Recover, Sysctl-Allowlist, Reboot-Guard) auf Regressionen — alle intakt.
Phase 2 — Analyse-Sub-Agenten
Vier parallele Analyse-Sub-Agenten: Security-Controller, Core & Services, Radio-Diff und — nach einem Abdeckungshinweis aus dem Radio-Diff — die Radio-Senken im Ist-Stand. Der Radio-Diff wurde über einen normalisierten Inhaltsdiff als reines Refactoring belegt statt zeilenweise gesichtet.
Phase 3 — False-Positive-Validierung
Je ein unabhängiger Validierungs-Sub-Agent mit dem
ausdrücklichen Auftrag, den Kandidaten zu widerlegen.
Einer extrahierte dazu das Original-ufw-Paket, der
andere testete das sysctl-Fehlerverhalten empirisch an
temporären Kopien. Ergebnis: 2/10 (widerlegt) und 6/10
(herabgestuft).
Phase 4 — Härtung & Regressionscheck
Acht Defense-in-Depth-Härtungen umgesetzt und mit 26 neuen Regressionstests abgesichert. Gesamtsuite danach: 2562 bestanden, 2 übersprungen — keine Regression.
Kandidaten & Validierung (Runde 11)
Kein Kandidat erreichte die Melde-Schwelle von 8/10, es gibt in dieser Runde also keine bestätigten Findings. Der Vollständigkeit halber ist unten dokumentiert, was gemeldet, wie es gegengeprüft und wie es eingestuft wurde — einschließlich der Teilbehauptungen, die sich als falsch erwiesen. Behoben wurden alle drei trotzdem.
K1 — stderr-Kontamination beim Read-Modify-Write von /etc/ufw/sysctl.conf
Niedrig · 6/10
Gehärtet
Datei:
netmon/features/security_audit_controller.py —
_apply_ufw_sysctl_overrides(),
_read_existing_sysctl_dropin().
Das neue Feature liest /etc/ufw/sysctl.conf per
sudo -n cat über
run_cmd, veraendert den Inhalt und schreibt ihn als
root zurück. run_cmd führt jedoch
stderr mit stdout zusammen: eine
sudo-Warnung wie
„unable to resolve host …“
erscheint mit Exit-Code 0 im vermeintlichen
Dateiinhalt, passiert die Filter unverändert und landet als
Müllzeile in einer Root-Konfigdatei.
Was die Validierung widerlegt hat: Der Melder
behauptete, die Härtungswerte könnten dadurch beim Boot
ausfallen. Empirisch geprüft (procps-ng 4.0.4, temporäre
Kopien): sysctl warnt bei einer Syntax-Müllzeile,
verarbeitet die Folgezeilen aber weiter und liefert
rc = 0; ufw-init ignoriert Fehler ohnehin per
|| true. Kein Härtungsverlust.
Übersehen hatte der Melder außerdem, dass ein
Zurückschreiben nur bei tatsächlicher Änderung
stattfindet, und dass dasselbe Muster bereits committed existiert
— neu eingeführt wurde es also nicht. Der Inhalt der
sudo-Warnungen ist zudem nicht angreiferkontrolliert
(Hostname/PAM/Audit), es gibt keinen Injektionsvektor.
_implausible_sysctl_config_line()) bricht den Lauf mit
klarer Meldung ab, sobald der gelesene Inhalt eine Zeile
enthält, die keine sysctl-Konfigzeile sein kann — an
beiden Lesestellen, also auch am bereits bestehenden
Drop-in-Pfad. Bewusst Abbruch statt stillem Verwerfen: eine
unerwartete Zeile in einer Root-Konfigdatei darf nicht kommentarlos
gelöscht werden.
K2 — Neue Schreibsenke ohne die Allowlist ihrer Schwester-Senke
Niedrig · 3/10 GehärtetDatei:
netmon/features/security_audit_controller.py —
_build_ufw_sysctl_override_content().
Die in Runde 10 eingezogene Schlüssel-Allowlist sitzt
bewusst direkt an der Schreibsenke
(_render_sysctl_dropin_content()) — als zweite
Verteidigungslinie für den Fall, dass ein Spec auf anderem Weg
als über die Transfer-Import-Validierung hereinkommt. Die neue
Senke schreibt nach /etc/ufw/sysctl.conf, eine Datei,
die laut eigenem Header /etc/sysctl.conf und
/etc/sysctl.d überschreibt —
wirkt also eher stärker als die Drop-in-Datei, trug aber
keine dieser Prüfungen.
Heute nicht ausnutzbar: Der einzige Aufrufer übergibt eine Modulkonstante; es existiert kein Datenfluss von Transferpaket, Remote-JSON oder Dateinamen zu dieser Senke. Das wurde explizit nachverfolgt. Gemeldet als Defense-in-Depth-Regression gegen eine dokumentierte Schutzentscheidung, nicht als Exploit.
SECURITY_BASELINE_ALLOWED_SYSCTL_KEYS-Allowlist
und dieselbe Wert-Validierung wie ihre Schwester-Senke. Ein Eintrag
wie kernel.core_pattern = |/tmp/payload oder ein
Wert mit eingebettetem Zeilenumbruch wird verworfen, statt als root
persistiert zu werden.
K3 — „Sysctl-Re-Apply dreht Kernelhärtung still zurück“
Widerlegt · 2/10
Gemeldet wurde, das unbedingte Neuladen von
/etc/ufw/sysctl.conf am Ende des UFW-Schritts könne
NetMons eigene Kernelhärtung überschreiben — konkret
rp_filter.
Widerlegt an drei Stellen. Erstens empirisch: der
Validierer extrahierte das Original-Paket
ufw_0.36.2-6_all.deb und wies nach, dass
/etc/ufw/sysctl.conf überhaupt kein
rp_filter enthält — weder gesetzt
noch auskommentiert. Die tragende Prämisse war falsch.
Zweitens: die einzige reale Überschneidung mit der
NetMon-Baseline ist log_martians — und
genau dafür existiert der beanstandete Code.
Die Paketdatei liefert log_martians=0 gegen NetMons
Soll 1; das neue Override korrigiert das, bevor
neu geladen wird. Der Melder hatte die Gegenmaßnahme als Bug
gemeldet. Drittens: eine nachgelagerte Wert-Verifikation existiert
sehr wohl — nach allen Schritten läuft ein erneuter
Baseline-Check, der die Live-Kernelwerte liest. Eine stille
Rückdrehung ist strukturell ausgeschlossen.
Hinzu kommt ein Denkfehler in der Richtung der Abweichung:
rp_filter=1 (strict) ist strenger als NetMons
bewusstes 2 (loose). Selbst im hypothetischen Fall
würde also die Sicherheit steigen und allein NetMons
Funktionsabsicht (VPN / Multi-Interface) leiden —
und das wäre in der UI als „Bewusste Abweichung“
sichtbar, nicht still.
log_martians-Gegenmaßnahme wirkungslos machen.
Weitere Beobachtungen & Design-Entscheidungen
Scope-Lücke geschlossen — die Radio-Senken lagen nie in einem Diff
Der Radio-Diff-Agent stellte fest, dass die eigentlich
sicherheitsrelevanten Radio-Pfade — mpv-/ffmpeg-Start,
ICY-Metadaten, Aufnahme-Dateinamen, Playlist-Parsing — gar
nicht in den geänderten Dateien liegen, sondern in
radio_controller.py und
netmon_radio_core.py. Beide sind seit Runden
unverändert und fielen damit systematisch durch jeden
Diff-basierten Scope. Sie wurden in dieser Runde erstmals im
Ist-Stand geprüft — mit vier Härtungen als Ergebnis.
Konsequenz für kommende Runden: lange
unveränderte Senken periodisch im Ist-Stand prüfen, nicht
nur den Diff.
Aufnahme-Dateinamen aus ICY-Metadaten — sauber
Der naheliegendste Verdacht im Radio-Modul bestätigte sich nicht: Stationsname und ICY-Titel laufen beide durch eine Slug-Funktion, die alles außer Kleinbuchstaben und Ziffern ersetzt. Pfad-Traversal, Newlines, Null-Bytes, Shell-Metazeichen und führende Bindestriche sind damit konstruktiv ausgeschlossen. Ebenso sauber: die Stream-URL-Schema-Allowlist wird auf allen Lade- und Speicherpfaden erzwungen, der EQ-Filter ist auf ganzzahlige Werte geklemmt, und der projectM-ZIP-Import ist gegen Zip-Slip abgesichert.
3.000 Zeilen Radio-Refactoring — ohne Verhaltensänderung belegt
Statt den großen Refactoring-Diff zeilenweise zu sichten, wurde ein normalisierter Inhaltsdiff gefahren: beide Fassungen whitespace-normalisiert, sortiert und verglichen. Ergebnis: die entfernten Zeilen sind ausnahmslos State-Initialisierungen und Geometrie-Ausdrücke, die 1:1 in neue Hilfsfunktionen gewandert sind. Die Whitelist beim Laden der Sprachdatei — ohne die ein Pfad-Traversal über die Konfigdatei möglich wäre — hat das Refactoring nachweislich überlebt.
UFW-Konflikthinweis im rp_filter-Zweig — geprüft, vorhanden
Bei der Widerlegung von K3 wurde zusätzlich behauptet, den
erklärenden Hinweis „/etc/ufw/sysctl.conf
setzt denselben Wert anders“ erzeuge nur der
sysctl-Zweig der Baseline-Auswertung, nicht der
rp_filter-Zweig — eine Diagnose-Lücke also.
Auch das hat der Nachtest widerlegt. Der
rp_filter-Zweig ruft nicht den Low-Level-Helfer
_sysctl_ufw_override_value() auf, nach dem gesucht
wurde, sondern den Wrapper
_sysctl_ufw_override_hint(), und hängt dessen
Ergebnis an die Begründung an. Beide
rp_filter-Specs tragen das dazu nötige
config_source. End-to-End nachgestellt (Drop-in
setzt 2, ufw-Datei setzt 1, Kernel meldet 1) liefert
die Auswertung Status „Bewusste Abweichung“
samt ufw-Hinweis. Keine Lücke, kein
Handlungsbedarf.
Kindersicherung — weiterhin bewusste Design-Entscheidung
Die PBKDF2-gestützte Kindersicherung im Secure-Browser bleibt wie in Runde 8 dokumentiert: Alltagshürde gegen Gelegenheitszugriff im selben Konto, keine harte Grenze gegen technisch versierte Nutzer mit Kontozugriff (No-Root-Design). Keine Codeänderung, unverändert gültig.
postinst-DNS & Nur-Hash-Schnellprüfung — Policy-Hinweise weiter offen
Die beiden Policy-Hinweise aus Runde 9 bleiben unverändert
bestehen: das root-postinst biegt aktive NM-DNS bei der
Installation auf 8.8.8.8 (Opt-in empfohlen), und die
„Schnellprüfung“ auf der Downloadseite empfiehlt nur
sha256sum -c statt der minisign-Vollprüfung.
Kein ausnutzbarer Befund — als Design-Entscheidungen des
Projektverantwortlichen offen.
Umgesetzte Härtungsmaßnahmen (Runde 11)
Acht Härtungen, keine davon durch ein bestätigtes Finding erzwungen — alle als Defense in Depth umgesetzt, jede mit Regressionstests abgesichert.
Allowlist an der UFW-sysctl-Schreibsenke — security_audit_controller.py
_build_ufw_sysctl_override_content() prüft
Schlüssel und Werte jetzt gegen dieselbe Allowlist und
dieselben Format-Regeln wie die Drop-in-Senke. Unerlaubte
Schlüssel und Werte mit Zeilenumbrüchen oder
Shell-Metazeichen werden verworfen.
Plausibilitätsgate gegen stderr-Kontamination — security_audit_controller.py
_implausible_sysctl_config_line() bricht jeden
Read-Modify-Write ab, dessen gelesener Inhalt eine Zeile
enthält, die weder leer, noch Kommentar, noch
key = value ist — an beiden
Lesestellen. Verhindert, dass eine sudo-Warnung mit
Exit-Code 0 als root in eine Root-Konfigdatei
zurückgeschrieben wird.
---Optionsterminator vor dem mpv-Medienargument — radio_controller.py
mpv wertet Optionen an jeder Position aus. Ein
Playlist-Eintrag namens
--script=payload.lua wäre als Option geladen
worden statt als Dateiname. Das Medienargument steht jetzt hinter
einem --; der EQ-Filter wird korrekt davor
einsortiert.
Handover-Payload durchläuft die URL-Allowlist — radio_controller.py
Die einzige Stelle im Modul, an der ein Stations-Datensatz ohne
normalize_station_entry() in den App-Zustand gelangte
— und damit die Stream-URL-Schema-Allowlist umging, die
überall sonst erzwungen wird. Ein file://-Eintrag
im Übergabe-Payload wird jetzt verworfen.
mpv-IPC-Socket in ein privates Verzeichnis — radio_controller.py
Wer auf diesen Socket schreiben darf, darf mpv steuern — und
mpv kennt ein run-Kommando. Der Socket lag bisher
direkt im State-Verzeichnis (typisch 0755). Er liegt jetzt in
XDG_RUNTIME_DIR, ersatzweise in einem eigens
angelegten 0700-Unterverzeichnis.
ffmpeg-Protokoll-Allowlist ohne file — radio_controller.py
Die Aufnahme erhält jetzt ein explizites
-protocol_whitelist http,https,tcp,tls,crypto.
Ohne diese Grenze wählt ffmpeg den Demuxer nach Inhalt —
eine als Stream ausgelieferte HLS-/concat-Playlist konnte damit
lokale Dateien in die Aufnahme ziehen.
Wert-seitige Redaction im Messlog — core/performance_monitor.py
Die Redaction prüfte bisher nur den Metadaten-Schlüssel; ein Secret unter einem harmlosen Schlüssel wäre im Klartext im Log gelandet. Werte laufen jetzt zusätzlich durch dieselbe Redaction wie die Diagnosedaten — rekursiv auch in Listen und verschachtelten Dicts, ebenso der Fehlertext. Kein damaliger Aufrufer war betroffen; die Härtung schließt die Falle für künftige.
Symlink-sicherer, nutzerprivater Messlog — core/performance_monitor.py
Der Log wird jetzt mit
O_NOFOLLOW | O_APPEND und Modus
0600 geöffnet, die Rotation rührt
symlink-verlinkte Pfade nicht an. Ein untergeschobener Symlink kann
die Messwerte damit weder umlenken noch eine fremde Datei
überschreiben.
26 neue Regressionstests
Elf in test_netmon_lynis_output.py (Allowlist an der
UFW-Senke, Erkennung von stderr-Zeilen, Abbruch statt
Zurückschreiben, ufw-Konflikthinweis im
rp_filter-Zweig), neun in
test_netmon_radio_module.py (Optionsterminator,
EQ-Reihenfolge, Protokoll-Allowlist, Socket-Rechte,
inklusive Fehlerfallback, Handover-Normalisierung) und sechs in
test_performance_monitor.py (Wert-Redaction
verschachtelt, Symlink-Verweigerung, Dateirechte). Gesamtsuite
danach 2562 bestanden, 2 übersprungen.
Geprüfte Sicherheitskategorien
| Kategorie | Geprüft | Ergebnis |
|---|---|---|
Schreiben in root-eigene Konfig (/etc/ufw/sysctl.conf) |
Ja | Kein Finding — Allowlist + Plausibilitätsgate ergänzt |
| Argument-/Options-Injektion in externe Player (mpv, ffmpeg) | Ja | Kein Finding — -- + Protokoll-Allowlist ergänzt |
| Fremddaten in Dateinamen (ICY-Metadaten, Stationsnamen) | Ja | Kein Finding (Slug-Normalisierung) |
| URL-Schema-Allowlists (Streams, Logos, Homepages) | Ja | Durchgängig erzwungen; Handover-Lücke geschlossen |
Zentraler Kommandopfad (core/command.py) |
Ja | Kein Finding (nur Mess-Wrapper, argv unverändert) |
| Secret-Leaks in Logs, Support-Bundle und Diagnose | Ja | Kein Finding — Wert-Redaction ergänzt |
| Dateirechte, Symlink / TOCTOU an neuen Schreibpfaden | Ja | Kein Finding — O_NOFOLLOW + 0600 + 0700-Socketdir ergänzt |
| Repository-Guard, DNS-Service, Prozess-Guard (Refactorings) | Ja | Kein Finding (Detektionszweige vollständig erhalten) |
Unsichere Deserialisierung (pickle, eval, shell=True) |
Ja | Kein Finding |
| TLS-Verifikation und Redirect-Handling | Ja | Kein Finding (keine Abschaltung, kein http→file) |
| Autorisierung / Kindersicherung | Ja | Bewusste Design-Entscheidung |
| Regression der Runde-9/10-Härtungen | Ja | Alle intakt |
Auditor: Claude Fable 5 (Anthropic) —
vier Analyse-Sub-Agenten + zwei unabhängige
False-Positive-Validierungs-Sub-Agenten
Projektverantwortlicher: Sven Fröhlich
Datum: 29. Juli 2026
Analysierter Codestand:
Arbeitsstand v1.1.20 (Diff seit dem Runde-10-Endstand
445279ac, einschließlich uncommitteter
Änderungen), ergänzt um eine Ist-Stand-Prüfung der
Radio-Senken — Runde 11
Methode: Statische Code-Analyse + manuelle
Datenfluss-Verifikation + normalisierter Inhaltsdiff für die
Refactoring-Masse + konfidenz-gescorte FP-Validierung mit
empirischer Gegenprüfung (Paketextraktion,
sysctl-Verhaltenstests) + Umsetzung von acht
Härtungen mit 26 Regressionstests + Regressions-Gegenprüfung
der Vorrunden-Härtungen (Gesamtsuite 2562 bestanden,
2 übersprungen)
— 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.