Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions additional/python/tests/test_fixture_leak_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
"""Leak check for the shared fixtures in test/fixtures/.

The zorin_*.txt fixtures are real command outputs captured on a developer
machine and redacted by hand at capture time. This test is the safety net
that runs in CI: if a future capture ships with a private IP, a named home
directory, a real URL/domain, a user@host pair or a port in an unambiguous
form (port=/port:, host:port), the suite fails before it reaches the public
repository.

The check is heuristic — it complements, never replaces, the manual review
documented in test/fixtures/README.md.
"""

import os
import re
import unittest

# additional/python/tests/ -> repo root: four dirname levels (tests, python,
# additional, root).
REPO_ROOT = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
)
FIXTURE_DIR = os.path.join(REPO_ROOT, "test", "fixtures")

# Public suffixes considered harmless in fixtures. Anything else that looks
# like a dotted host is flagged.
ALLOWED_TLDS = r"com|net|org|io|dev|de|eu|info|biz|co|me|app|xyz|example"

# The one allowed URL placeholder; findings that point at it are dropped.
ALLOWED_URL = "example.invalid"

_IPV4_OCTET = r"(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])"

_PATTERNS = (
# 1. IPv4: four 0-255 octets, not embedded in a longer dotted token
# (keeps version strings like 1.2.3.4.5 and host names like a.b.c.d
# that carry a trailing label out of the match).
re.compile(
r"(?<![\w.])" + _IPV4_OCTET + r"(?:\." + _IPV4_OCTET + r"){3}(?![\w.])"
),
# 3. /home/<x> and /media/<x> with x != 'user' — 'user' is the only
# name the redaction rules keep.
re.compile(r"/(?:home|media)/(?!user\b)[^/\s]+"),
# 4. URL/domain with a TLD from the allowlist.
re.compile(r"[a-zA-Z0-9][a-zA-Z0-9.-]*\.(?:" + ALLOWED_TLDS + r")\b"),
# 5. user@host
re.compile(r"[A-Za-z0-9._-]+@[a-zA-Z0-9][a-zA-Z0-9.-]*\b"),
# 7. port in the unambiguous port= / port: forms (--port=41641,
# port: 5432). The space form (--port 7000) stays out on purpose:
# it is indistinguishable from ordinary argument values.
re.compile(r"(?i)\bport\s*[=:]\s*\d{1,5}\b"),
# 8. host:port — hostname-like label run (letters, digits, hyphens,
# dots; no underscores) directly before the colon. The colon must
# not follow a digit or colon, so clock times (14:23:01, up 3:45)
# and kernel thread names (259:0) stay out; display numbers with no
# host (vnc=:0, Xwayland :1) and identifier handles with underscores
# (snapshot_data:100) have no hostname before the colon.
re.compile(
r"(?<![\w.-])"
r"[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?"
r"(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*"
r"(?<![0-9:]):"
r"\d{1,5}(?![\w.])"
),
)


def _ipv6_candidates(text):
"""Heuristic IPv6 hits: runs of hex digits and colons containing '::'.

At least one decimal digit is required so that C++ scope separators
(std::vector) stay out; real addresses like ::1 or 2001:db8::1 carry
digits.
"""
for run in re.findall(r"[0-9A-Fa-f:]+", text):
if "::" in run and any(c.isdigit() for c in run):
yield run


def find_leaks(text: str) -> list:
"""Return the findings in *text* as strings; an empty list means clean."""
findings = []
for pattern in _PATTERNS:
findings.extend(pattern.findall(text))
findings.extend(_ipv6_candidates(text))

seen = set()
unique = []
for finding in findings:
# 6. Post-filter: the allowed URL placeholder never counts as a leak.
if ALLOWED_URL in finding:
continue
if finding not in seen:
seen.add(finding)
unique.append(finding)
return unique


class LeakDetection(unittest.TestCase):
MUST_FLAG = [
"192.168.178.23",
"2001:db8::1",
"connect to 10.0.0.5:5432",
"/home/bratan/secret.txt",
"/media/braten/USB",
"curl https://internal.corp.example/health",
"ssh git@github.com",
"port=5432",
"port: 41641",
"connect host:8080",
"tcp:db-server.internal:7149",
]
MUST_NOT_FLAG = [
" 14:23:01 up 3:45, 1 user, load average: 0.52, 0.58",
"udev 7,8G 0 7,8G 0% /dev",
"/usr/lib/firefox/firefox",
"libGL.so.1",
"python3.12",
"/home/user/notes.txt",
"/media/user/USB",
"https://example.invalid/x",
"/dev/nvme0n1p2",
"%CPU COMMAND",
"14:23:01 up 3:45",
"0.52, 0.58",
"--port 7000",
"vnc=:0,websocket=5700",
"Xwayland :1",
"--shared-files=v8_context_snapshot_data:100",
]

def test_every_must_flag_vector_is_detected(self):
for vector in self.MUST_FLAG:
self.assertTrue(find_leaks(vector), "not flagged: %r" % vector)

def test_no_must_not_flag_vector_is_detected(self):
for vector in self.MUST_NOT_FLAG:
self.assertEqual(find_leaks(vector), [], "flagged: %r" % vector)


class FixturesClean(unittest.TestCase):
"""Every fixture under test/fixtures/ survives the leak check.

Only *.txt files are scanned. README.md is deliberately excluded: it
documents the redaction rules and the allowed placeholders, so it
contains exactly the kinds of strings this check exists to catch and
would fail by design.
"""

ZORIN_FIXTURES = [
"zorin_df.txt",
"zorin_free.txt",
"zorin_loadavg.txt",
"zorin_ps.txt",
"zorin_uptime.txt",
]

def test_the_five_real_zorin_captures_exist(self):
for name in self.ZORIN_FIXTURES:
self.assertTrue(
os.path.isfile(os.path.join(FIXTURE_DIR, name)),
"missing fixture: %s" % name,
)

def test_every_txt_fixture_is_free_of_leaks(self):
self.assertTrue(os.path.isdir(FIXTURE_DIR), "%s is missing" % FIXTURE_DIR)
names = sorted(n for n in os.listdir(FIXTURE_DIR) if n.endswith(".txt"))
self.assertTrue(names, "no *.txt fixtures under %s" % FIXTURE_DIR)
for name in names:
with open(os.path.join(FIXTURE_DIR, name), encoding="utf-8") as handle:
content = handle.read()
self.assertEqual(find_leaks(content), [], "%s contains leaks" % name)


if __name__ == "__main__":
unittest.main()
16 changes: 8 additions & 8 deletions docs/mla-next/ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,16 @@ _Quelle: `docs/mla-next/AGENT_PLAN.md` (A3), `docs/mla-next/VERIFY.md` (Gate 1).
Eine Fixture-Bibliothek, die Flutter- und GTK-Track gemeinsam nutzen (echte, geschwärzte Zorin-Ausgaben; Ablageort legt der Spec fest — `test/fixtures/` existiert noch nicht). Dazu Fehler- und Stale-Modelle: `unknown`, `stale`, `failed`, `running`, `ok` sind getrennte Zustände; `stale` ist UI-/Transportstatus und wird nie stillschweigend zu `ok` (`IPC_CONTRACT.md`).

## Abnahme
- [ ] Fixtures liegen an einem Ort; beide Tracks lesen sie, kein Duplikat
- [ ] Schwärzung dokumentiert; Leak-Check (keine Hosts, IPs, Ports, `/home`-Pfade) auf allen Fixtures leer
- [ ] Tests für jeden Zustand, den Übergang ok → stale und den Fehlerpfad
- [ ] Abgrenzung zu QA3 (#87, V1.0): dieser Task liefert die Basis, QA3 konsolidiert den Bestand
- [x] Fixtures liegen an einem Ort; beide Tracks lesen sie, kein Duplikat — 2026-09-30: `test/fixtures/` (5 echte + 7 synthetische), App- und la_core-Suite laden jede der 12 Dateien, keine Inline-Multi-Zeilen-Samples mehr (kleine Robustheits-Literale wie `"header\nMem: not a number\n"` blieben plan-gemäß inline; Review Task 3 B, eigenständig reproduziert)
- [x] Schwärzung dokumentiert; Leak-Check (keine Hosts, IPs, `/home`-Pfade; Ports in `port=`-/`port:`- und `host:port`-Form) auf allen Fixtures leer — 2026-09-30: `test/fixtures/README.md`; `additional/python/tests/test_fixture_leak_check.py` läuft mit jedem CI-Lauf, leer auf allen `*.txt`; TLD-Lücke `.ai` dokumentiert, manuelle Durchsicht blieb Pflicht und fing eine Konto-URL; Final-Review-Fix: Brave-Client-ID (persistent, 4×) geschwärzt, `--port=41641`/`localhost:7100`/`tcp:<redacted>:7149` geschwärzt, bloße Port-Zahlen in Args (`websocket=5700`, `--port 7000`) in der README dokumentiert behalten
- [x] Tests für jeden Zustand, den Übergang ok → stale und den Fehlerpfad — 2026-09-30: `packages/la_core/test/probe_status_test.dart` (8 Tests: Invarianten je Zustand, ok→stale behält data/observedAt, markStale wirft außerhalb ok, Fehlerpfad mit error)
- [x] Abgrenzung zu QA3 (#87, V1.0): dieser Task liefert die Basis, QA3 konsolidiert den Bestand — 2026-09-30: `test/system_monitor_service_test.dart` (synthetische `/proc`-Konstanten) unberührt gelassen; Bestandskonsolidierung bleibt #87

## Handoff (Pflicht je Aufgabe, aus `docs/mla-next/VERIFY.md`)
- [ ] Basis-SHA, Pfade, Scope und Failing-Test/Fixture stehen vor der Umsetzung fest
- [ ] Reviewer 1 (Funktion/UX/Races) und Reviewer 2 (Privilegien/Secrets/argv/IPC) haben geprüft
- [ ] Wirklich ausgeführte Gates mit Ausgabe; rote oder übersprungene Gates benannt; Rückfallplan genannt
- [ ] Kein Merge, Release oder Policy-Update ohne gesonderte Freigabe
- [x] Basis-SHA, Pfade, Scope und Failing-Test/Fixture stehen vor der Umsetzung fest — 2026-09-30: SDD-Plan `docs/superpowers/plans/2026-09-30-mla-94-fixtures-state-models.md` (Basis `e4c1346`, Briefs je Task)
- [x] Reviewer 1 (Funktion/UX/Races) und Reviewer 2 (Privilegien/Secrets/argv/IPC) haben geprüft — 2026-09-30: je Task zwei parallele Reviewer (A Korrektheit/B Vollständigkeit) + Secrets-Sicht, Verlauf im Abschnitt „Handoff #94"; Task 4 mit Fix-Runde und Re-Review
- [x] Wirklich ausgeführte Gates mit Ausgabe; rote oder übersprungene Gates benannt; Rückfallplan genannt — 2026-09-30: VERIFY.md „Handoff #94"-Gatetabelle (flutter 200/200, la_core 60/60, Python 53/53, check-versions ok); rot nur geplante TDD-REDs; Rückfallplan `git revert` dokumentiert
- [x] Kein Merge, Release oder Policy-Update ohne gesonderte Freigabe — 2026-09-30: Branch `feature/mla-94-fixtures` nicht gepusht; Merge/Freigabe ausstehend

## Abhängigkeiten
Blockiert durch: #93
Expand Down
48 changes: 47 additions & 1 deletion docs/mla-next/VERIFY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MLA-Next: Verifikation und Handoff

Aktualisierung 2026-09-30: Die GTK-Scaffold-Laufzeit ist auf Zorin verifiziert (Wayland- und X11-Start — Gate 0, BASELINE §2; manuelle Checks offen), la_core-Spike und Registry sind getestet inkl. Performance-Messwerten (Gate 1, BASELINE §7). Weiterhin nicht ausgeführt: IPC-/Gate-2-Tests, Flutter-vs-GTK-Vergleichsmessung (Roadmap 0.0.3), manuelle Gate-0-Checks (BASELINE §3). Der Scaffold-Commit ist **kein** Release-Gate.
Aktualisierung 2026-09-30: Die GTK-Scaffold-Laufzeit ist auf Zorin verifiziert (Wayland- und X11-Start — Gate 0, BASELINE §2; manuelle Checks offen), la_core-Spike und Registry sind getestet inkl. Performance-Messwerten (Gate 1, BASELINE §7). **#93 ist gemerged** (PR #89 → `6c5c625`, PR #107 → `e4c1346`; Merge-Freigabe 2026-09-30) und **#94 (Fixtures + Fehler-/Stale-Modelle) auf `feature/mla-94-fixtures` umgesetzt** (Handoff-Abschnitt unten, alle Gates grün, wartet auf Final-Review/Freigabe). Weiterhin nicht ausgeführt: IPC-/Gate-2-Tests, Flutter-vs-GTK-Vergleichsmessung (#95), manuelle Gate-0-Checks (BASELINE §3). Der Scaffold-Commit ist **kein** Release-Gate.

## Gate 0: Scaffold

Expand All @@ -16,6 +16,7 @@ Aktualisierung 2026-09-30: Die GTK-Scaffold-Laufzeit ist auf Zorin verifiziert (
- [x] Issue #60: IDs eindeutig, fehlende/zyklische Abhängigkeiten abgewiesen, Start/Stop/Lazy-Loading getestet; Flutter-Navigation unverändert. — Kern-Registry-Teil 2026-09-30: `dart test` 30/30 in packages/la_core (doppelte IDs, fehlende/zyklische Abhängigkeiten, Topo-Start/Rückwärts-Stop, Single-Flight); `git diff --stat 92bef60..HEAD -- lib/ additional/ deb/ linux/` leer (Exit 0). **Der Flutter-`HubModule`-Adapter samt Vollständigkeitstest ist nachgeliefert** — Belege im Abschnitt „Abnahme #60" unten (Root-`flutter test` +200)
- [x] Bestehende Repo-Gates nach Scope tatsächlich ausführen: `tool/check-versions.sh`, `dart format`, `flutter analyze`, `flutter test`, Python-Tests. — 2026-09-30 frisch ausgeführt, alle Exit 0: `version 0.8.0 is consistent`; `Formatted 118 files (0 changed)`; `No issues found!`; `00:02 +184: All tests passed!`; `Ran 49 tests` / `OK` — vgl. docs/mla-next/BASELINE.md §4
- [x] Issue #93, Schnitt 1 (Parser-Umzug nach `packages/la_core`): App-Test byte-identisch, alle Gates grün. — 2026-09-30: Handoff-Abschnitt unten. DI, Event-Vertrag und `la_probe`-Ablage sind in **Schnitt 2** geliefert (Abschnitt „Handoff #93 Schnitt 2" unten, alle Gates grün); der Flutter-`HubModule`-Adapter ist mit #60 geliefert (Abschnitt unten).
- [x] Issue #94 (Gemeinsame Fixtures + Fehler-/Stale-Modelle): beide Tracks lesen `test/fixtures/`, Leak-Check leer, `ProbeStatus` getestet. — 2026-09-30: Handoff-Abschnitt „Handoff #94" unten; alle Gates grün (flutter 200/200, la_core 60/60, Python 53/53).

## Gate 2: IPC und Backup

Expand Down Expand Up @@ -338,6 +339,51 @@ Listener + `onListenerError`, Nutzung nach `dispose` → `EventBusError`), `prob
**Rückfallplan.** `git revert` der Schnitt-2-Commits genügt: kein Migrationsschritt, kein Datenpfad, keine Unit
in `deb/DEBIAN/control`, `la_core` nirgends installiert.

## Handoff #94 — Gemeinsame Fixtures + Fehler-/Stale-Modelle

**Status:** umgesetzt auf `feature/mla-94-fixtures` (Basis `e4c1346`, 6 eigene Commits `aff40ac..3786464`), **nicht gepusht** — Push/PR folgen; **Merge und Schließen von #94 erst nach gesonderter Freigabe.**

**Basis-SHA/Pfade/Scope.** Basis `e4c1346` (main, nach PR-#107-Squash). Neu: `test/fixtures/` (fünf echte geschwärzte Zorin-Ausgaben `zorin_df/ps/uptime/free/loadavg.txt` + sieben synthetische Edge-Vektoren + `README.md` mit Capture-Kommandos, Schwärzungsregeln, GTK-/Python-Track-Abschnitt), `additional/python/tests/test_fixture_leak_check.py`, `packages/la_core/lib/src/probe_status.dart` (+ Barrel-Export), `packages/la_core/test/probe_status_test.dart`, SDD-Plan `docs/superpowers/plans/2026-09-30-mla-94-fixtures-state-models.md`. Geändert: `test/system_parsers_test.dart`, `packages/la_core/test/parsers_test.dart` (lesen nun die Fixtures statt Inline-Duplikate). Grenzen eingehalten: `test/system_monitor_service_test.dart` unberührt (QA3/#87), kein Code in `prototype/gtk/` (A2/#92), keine IPC-Contract-Fixtures (`IPC_CONTRACT.md:22`), polkit-Dreifaltigkeit unberührt.

**Failing-Test/Fixture.** Task 1: RED `FixturesClean` ohne `test/fixtures/` (zwei Failures, Output im Task-Report). Task 2: RED Compile-Fehler `Couldn't find constructor 'ProbeStatus'`. Task 3: Umbau bestehender Tests — Baseline-Vorher-Lauf dokumentiert (flutter 200/200 vor wie nach).

**Umsetzung.** Subagent-Driven Development: je Task frischer Implementer + Two-Reviewer-Gate (Reviewer A Funktion/Korrektheit, Reviewer B Vollständigkeit/Spec), Reports unter `.superpowers/sdd/task-{1..4}-{report,review*}.md`, Ledger `.superpowers/sdd/progress.md`.

**Zustandsmodell.** `ProbeState { unknown, running, ok, stale, failed }`; `stale` nur über `markStale()` aus `ok` (behält `data`/`observedAt`), kein Pfad zurück zu `ok` ohne frische Observation — 8 neue Tests, Vertragslage `IPC_CONTRACT.md:16`.

**Schwärzung/Leak-Check.** Capture exakt der Produktions-Aufrufe (df ohne LC_ALL, uptime/free mit `LC_ALL=C`, ps `-eo pcpu,args --sort=-pcpu`, `/proc/loadavg`), alles unprivilegiert. Schwärzung: `/home|/media/<name>` → `/user`-Platzhalter, Usernamen → `user`, URLs/Hosts → `example.invalid`; über die Regeln hinaus zusätzlich geschwärzt: QEMU-SMBIOS-Serial, MAC-Adresse, Xwayland-Authority-Suffix, eine Konto-URL (`.ai`-TLD, vom Leak-Check nicht abgedeckt — Lücke in `test/fixtures/README.md` dokumentiert, manuelle Durchsicht bleibt Pflicht). Leak-Check läuft bei jedem CI-Lauf mit (Trigger ausschließlich Push/PR) und ist auf allen zwölf `*.txt` leer. Final-Review-Fix (eigener Commit): die Brave-Crash-Reporter-Client-ID — persistent pro Installation, in der ersten Fassung fälschlich als Session-Zufallswert geführt — wurde 4× in `zorin_ps.txt` zu `<redacted>` geschwärzt; der Leak-Check prüft nun zusätzlich Ports in `port=`-/`port:`- und `host:port`-Form, wodurch `--port=41641`, `telnet:localhost:7100` und `tcp:<redacted>:7149` gleichfalls geschwärzt wurden; die verbleibenden bloßen Port-Zahlen (`websocket=5700`, `--port 7000`) sind in `test/fixtures/README.md` dokumentiert.

**Reviewer.** Je Task A (Korrektheit) + B (Vollständigkeit) parallel:
- Task 1: A APPROVED / B SPEC_OK — Werte und Scope unabhängig verifiziert; Minor: TLD-Allowlist-Lücke, `/opt/brave.com`-Fehlalarm (im Fixture neutralisiert), keine `subTest`s.
- Task 2: A APPROVED / B SPEC_OK — „kein stillschweigender stale→ok-Pfad" konstruktiv geprüft (`final class`, `_stale` privat, `ok` nur frischer Konstruktor); Gates von B reproduziert (60/60).
- Task 3: A APPROVED / B SPEC_OK — alle gepinnten Erwartungswerte gegen die Fixture-Dateien nachgerechnet; Asserts teils gestrafft (df `hasLength(2)`, beide `_removableDevices`-Einträge abgedeckt).
- Task 4: A NEEDS_FIXES (F1 dirname-Zählung, F2 „täglich in CI", F3 Präsens-Overclaim) → Fix `3786464` → Re-Review A APPROVED / B SPEC_OK.
- Secrets/Privilegien-Sicht (Reviewer-2-Pflicht aus dem Issue): unprivilegierte Captures, Schwärzung inkl. Zusatzfunde oben, Leak-Check-Vektoren (MUST_FLAG/MUST_NOT_FLAG) grün, kein `pkexec`/polkit-Bezug im Diff.

**Gates — tatsächlich ausgeführt (2026-09-30 auf `3786464`), alle Exit 0.**

| Gate | Ausgabe |
|---|---|
| la_core `dart format --output=none --set-exit-if-changed lib test` | `Formatted 23 files (0 changed)` |
| la_core `dart analyze` | `No issues found!` |
| la_core `dart test` | `00:00 +60: All tests passed!` (52 alt + 8 neu) |
| `dart compile exe bin/la_probe.dart` + display-less `--version`-Lauf | `Generated: /tmp/la_probe94`; `la_probe 0.0.1-spike.1 (dart 3.13.4 … linux_x64)` |
| Root `dart format --output=none --set-exit-if-changed lib test` | `Formatted 122 files (0 changed)` |
| `flutter analyze` | `No issues found! (ran in 3.1s)` |
| `flutter test` (voller Lauf) | `00:07 +200: All tests passed!` |
| `python3 -m unittest discover -s tests -t .` (additional/python) | `Ran 53 tests` / `OK` (49 alt + 4 neu) |
| `bash tool/check-versions.sh` | `version 0.8.0 is consistent` |

Nach der Final-Review-Fix-Runde `8bddeb6` erneut ausgeführt und grün: Python `Ran 53 tests` / `OK`, `flutter test` `+200: All tests passed!`,
la_core `dart test` `+60: All tests passed!` (Belege in `.superpowers/sdd/final-review-94-fix-report.md`; der Re-Review hat den Python-Lauf
unabhängig reproduziert).

**Rote/übersprungene Gates.** Rot nur die geplanten TDD-REDs (Task 1 `FixturesClean`, Task 2 Compile-Fehler). Übersprungen: `build-deb.sh` (kein Paketbezug — `la_probe`-Kompilat direkt geprüft; CI baut beim PR), CI für den Branch (läuft mit dem späteren PR), manuelle Gate-0-Checks (unverändert offen, von #94 nicht berührt).

**Manuelle Zorin-Prüfung.** Für #94 nicht erforderlich (keine UI-Änderung); die Captures stammen von diesem Zorin-Rechner (2026-09-30).

**Rückfallplan.** `git revert` der Task-Commits genügt: Task 2 isoliert (nur la_core-Neudatei + Barrel-Zeile), Task 1+3 gemeinsam (gemeinsame Fixture-Dateien), Task 4 reine Doku. Kein Migrationsschritt, kein Datenpfad.

## Agenten-Handoff

Je Aufgabe: Basis-SHA, Pfade, Scope, Failing-Test/Fixture, Umsetzung, Ergebnis von Reviewer 1 (Funktion/UX) und Reviewer 2 (Sicherheit), **wirklich ausgeführte** Gates mit Ausgaben, rote/übersprungene Gates, manuelle Zorin-Prüfung, Rückfallplan. Kein Merge/Release/Policy-Update ohne gesonderte Freigabe.
Loading
Loading