docs: Gate 5 for #0106 — half the estate had never been asked
Deployed by sorb, then measured rather than assumed: 56 targets, coverage at a hundred percent, no orphaned report, and the round completing in about two minutes. Every blind spot named in the issue now carries a report and both stale entries are gone. The news is not that the pipeline was broken. It answered reliably for the twenty-nine images it knew about. The other twenty-seven had never been asked, and they carry a hundred and twenty-seven additional critical findings — including the public wiki, both Traefik layers, Prometheus, Grafana, Alertmanager and the scanner's own image. That belongs to #0051, which is updated with the new baseline; one of its candidates is not fixable but removable, since an unused registry tag only entered the set because its repository has fewer tags than the selection depth. Three of my own numbers were wrong and are corrected in place: sixty-five targets became fifty-six, up to sixty-five messages became thirty-nine, and the normalisation changed every alert fingerprint so all known findings reported once more. The last one was foreseeable from Gate 2, where normalisation is already listed as mandatory; I did not follow it through to the alerting layer. The shakiest call of Gate 3 measured better than feared. cAdvisor sees all eight images the compose file declares and four more that appear in no compose file of ours — the registry itself among them. Deriving from the running host beats deriving from its description. A new stolperstein carries the cheapest lesson of the day: a test that rebuilds the unit under test proves the rebuild. Loading the real function exposed within one run that the shipped file ignored its environment entirely.
This commit is contained in:
@@ -7,7 +7,7 @@ als Wiki-Seiten unter [docs/wiki/stolpersteine/](docs/wiki/index.md)
|
||||
— eine Seite je Muster, mit Stand und, wo zutreffend, der Version,
|
||||
die es abdeckt (ADR-0009 des Rahmenwerks).
|
||||
|
||||
**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 6 offen, 3 teilweise, 4 geerntet, 2 nicht abgedeckt.
|
||||
**Die geclusterte Übersicht steht in [STATUS.md](STATUS.md)**, Abschnitt *Fehlerklassen*: 7 offen, 3 teilweise, 4 geerntet, 2 nicht abgedeckt.
|
||||
|
||||
Diese Datei hält keinen eigenen Bestand mehr. Sie bleibt als
|
||||
Wegweiser bestehen, weil angenommene Entscheidungen auf sie
|
||||
|
||||
@@ -128,15 +128,16 @@ Bedeutung der Meilensteine: siehe [roadmap.md](roadmap.md).
|
||||
- [AAR — Game-Host-Anbindung: Deploy über zwei Übergaben, Ursache war nie die Firewall](docs/aar/2026-08-20-game-host-anbindung.md)
|
||||
- [AAR — rohana lief voll, der ThreadNet-Web-Spiegel riss](docs/aar/2026-08-20-rohana-platte-voll-spiegel-gerissen.md)
|
||||
|
||||
## Fehlerklassen (17)
|
||||
## Fehlerklassen (18)
|
||||
|
||||
### offen (6)
|
||||
### offen (7)
|
||||
|
||||
- [Die AAR-Pflicht setzt sich ohne Werkzeug nicht durch](docs/wiki/stolpersteine/aar-pflicht-ohne-werkzeug.md)
|
||||
- [Prüfungen erzeugen Befunde, die niemand beheben kann](docs/wiki/stolpersteine/befunde-die-niemand-beheben-kann.md)
|
||||
- [Ein gleichzeitiger, sachfremder Defekt wird der eigenen Änderung angelastet](docs/wiki/stolpersteine/fremder-defekt-der-aenderung-angelastet.md)
|
||||
- [Issues behaupten Zustände, die längst überholt sind](docs/wiki/stolpersteine/issues-behaupten-ueberholte-zustaende.md)
|
||||
- [Eine Datei kann nicht maßgeblich und zugleich unveränderlich sein](docs/wiki/stolpersteine/massgeblich-aber-unveraenderlich.md)
|
||||
- [Ein nachgebauter Test prüft den Nachbau](docs/wiki/stolpersteine/nachgebauter-test-prueft-den-nachbau.md)
|
||||
- [Eine Quittung bezeugt Aufmerksamkeit, nicht Vollständigkeit](docs/wiki/stolpersteine/quittung-bezeugt-aufmerksamkeit.md)
|
||||
|
||||
### teilweise gedeckt (3)
|
||||
|
||||
@@ -599,3 +599,115 @@ Zusicherungen grün · **13** Shell-Zusicherungen grün · `sh -n` beide ·
|
||||
|
||||
**Weicht 1–3 ab, ist die Herleitung falsch — nicht die Handmessung überholt.**
|
||||
Dann nicht weiterfahren, sondern den Unterschied ausmessen.
|
||||
|
||||
### Slice 5 — durchgeführt, 2026-08-21 · Status: **DONE**
|
||||
|
||||
**5.1 — die wackeligste Annahme aus Gate 3, gemessen.** Befürchtet war: cAdvisor
|
||||
sieht nur, was *läuft*; ein gestoppter Dienst fehlt in der Soll-Menge, und die
|
||||
Deckung meldet trotzdem 100 %.
|
||||
|
||||
| | Zahl |
|
||||
|---|---|
|
||||
| im Compose deklarierte Images | 8 |
|
||||
| davon von cAdvisor gesehen | **8** — Lücke **0** |
|
||||
| zusätzlich gesehen, in **keinem** unserer Compose-Dateien | **4** |
|
||||
|
||||
Die vier sind `gitea/gitea:1.27.0` (**die Registry selbst**), `traefik:v3.7.9`,
|
||||
`portainer/agent:2.27.5` und `cadvisor`. **Eine aus dem Compose abgeleitete
|
||||
Liste hätte genau diese vier übersehen** — cAdvisor ist also nicht der
|
||||
schwächere, sondern der stärkere Weg. Das Restrisiko (ein gerade gestoppter
|
||||
Dienst) bleibt und ist heute mit **0** beziffert statt vermutet.
|
||||
|
||||
**5.2 — Kriterium 5** zeigt sich beim nächsten `threadnet-web`-Release von
|
||||
selbst und ist bis dahin offen.
|
||||
|
||||
## Gate 5 — Closeout (AAR)
|
||||
|
||||
**Planned vs. actual.** Geplant war ein Vorhaben, das eine Alarmregel gegen eine
|
||||
handgepflegte Liste baut. Nach sorbs Einwand wurde daraus der Wegfall der Liste
|
||||
— und damit ein anderes, kleineres und besseres Vorhaben. Fünf Slices statt
|
||||
einer Regel.
|
||||
|
||||
| Abnahmekriterium (Gate 1) | Ergebnis |
|
||||
|---|---|
|
||||
| 1 `images.txt` existiert nicht mehr | erfüllt, ersatzlos |
|
||||
| 2 Deckung 100 % | erfüllt: `cve_targets_missing` **0** bei 56 Zielen |
|
||||
| 3 kein Ziel ohne Grund | erfüllt: `orphaned` **0**; `v0.3.0` und `coturn:latest` sind fort |
|
||||
| 4 Deckung als Metrik, alarmierbar | erfüllt: fünf Serien, vier Regeln, drei Panels |
|
||||
| 5 ein Ausrollen genügt als Nachweis | **offen** bis zum nächsten Release |
|
||||
| 6 `gameserver_cadvisor` bleibt draußen | erfüllt |
|
||||
|
||||
**Der Ertrag, gemessen statt behauptet.**
|
||||
|
||||
| | vorher (29) | nachher (56) |
|
||||
|---|---|---|
|
||||
| Deckung | 52 % | **100 %** |
|
||||
| CRITICAL | 126 | **253** |
|
||||
| HIGH | 1222 | **3674** |
|
||||
|
||||
⚠️ **Die zuvor unsichtbare Hälfte trägt 127 zusätzliche CRITICAL-Befunde.** Zum
|
||||
ersten Mal überhaupt gemeldet: das öffentlich erreichbare Wiki, **beide**
|
||||
Traefik-Schichten, Prometheus, Grafana, Alertmanager, die k3s-Bausteine — und
|
||||
`aquasec/trivy` selbst. Nicht die Pipeline war kaputt; die Hälfte des Bestands
|
||||
war nie befragt worden.
|
||||
|
||||
**Was diese Arbeit falsch gemacht hat.**
|
||||
|
||||
1. ⚠️ **Mein erster Vorschlag war eine Krücke.** Eine Regel, die Abweichungen
|
||||
einer gepflegten Liste meldet, hält die Liste am Leben und fügt ihr eine
|
||||
Pflicht hinzu. sorbs Einwand („das muss automatisch passieren") hat das
|
||||
Vorhaben halbiert und verbessert. Ich hatte die Leiter nur bis „was fehlt in
|
||||
der Liste" bestiegen, nicht bis „braucht es die Liste".
|
||||
2. **Die Mengenschätzung war falsch.** „~65 Ziele" beruhte auf drei Tags je
|
||||
Repo; vier der sechs Repos haben nur **einen**. Tatsächlich 56 — erklärbar
|
||||
(51 laufend + 9 Registry − 4 Überschneidung), aber eben nachträglich.
|
||||
Ebenso „≤ 65 Meldungen": es waren **39**.
|
||||
3. ⚠️ **Ein Nebeneffekt, den ich hätte vorhersehen können.** Die Normalisierung
|
||||
ändert die `target`-Beschriftung (`docker.io/postgres:17-alpine` →
|
||||
`postgres:17-alpine`) und damit **jeden Alarm-Fingerabdruck**. Alle
|
||||
bekannten Befunde galten einmalig als neu und wurden erneut gemeldet. Das
|
||||
steht in keinem der Gates, obwohl Gate 2 die Normalisierung ausdrücklich als
|
||||
Pflicht führt — ich habe ihre Wirkung auf die Alarmschicht nicht zu Ende
|
||||
gedacht.
|
||||
4. **Drei Einrückungs- und Gruppierungsfehler** (Compose 4/6 statt 6/8, Alarme 6
|
||||
statt 8, `runde || sleep A && sleep B`). Alle von Kontrollen gefangen, keiner
|
||||
ausgeliefert.
|
||||
|
||||
**Why the difference.** Die drei Korrekturen, die etwas geändert haben, kamen
|
||||
von außen oder aus einer Messung: sorbs Einwand, der fehlgeschlagene Testlauf,
|
||||
der Vergleich Compose gegen cAdvisor. Keine kam aus dem Nachdenken über den
|
||||
eigenen Plan.
|
||||
|
||||
**Learnings.**
|
||||
|
||||
- ⚠️ **Ein nachgebauter Test prüft den Nachbau.** Der erste `test_scan_loop.sh`
|
||||
baute die Schleifenlogik nach und wäre grün geblieben, während die
|
||||
ausgelieferte Datei kaputt ist. Nach dem Umbau auf *Laden* der echten
|
||||
`runde()` fiel binnen **eines Laufs** auf, dass `scan-loop.sh` seine Pfade
|
||||
fest verdrahtete. Das ist die billigste Lehre des Tages und die
|
||||
übertragbarste.
|
||||
- **Eine Prüfung schuldet den Nachweis, dass sie rot werden kann** — hier
|
||||
viermal geführt (Normalisierung sabotiert: 11 von 21 rot; Alarmschwelle
|
||||
unerreichbar: rot; Karenz entfernt: rot; Wache entfernt: „Berichte
|
||||
unberührt" fällt von 2 auf 0).
|
||||
- **Wer Ziele ableitet, muss das Ausbleiben der Herleitung alarmieren.** Eine
|
||||
leere Soll-Menge sieht aus wie vollständige Deckung. Das ist der Kern von
|
||||
ADR-0026 und der Grund für `cve_target_source_stale`.
|
||||
- **Die laufende Anlage ist die bessere Quelle als ihre Beschreibung.** cAdvisor
|
||||
sah vier Images, die in **keiner** unserer Compose-Dateien stehen — darunter
|
||||
die Registry selbst.
|
||||
|
||||
**Harvested.** ADR-0026. Für die nächste Ernte vorgemerkt: die Lehre über
|
||||
nachgebaute Tests, als eigener Stolperstein.
|
||||
|
||||
**Open uncertainties.**
|
||||
|
||||
1. **Kriterium 5** ist bis zum nächsten Release unbewiesen.
|
||||
2. ⚠️ **127 neu sichtbare CRITICAL-Befunde sind unbearbeitet.** Sie gehören zu
|
||||
#0051, nicht hierher — aber sie sind ab heute sichtbar und laufen auf.
|
||||
3. **Ein gestoppter Dienst fehlt weiterhin still in der Soll-Menge.** Heute mit
|
||||
0 beziffert, morgen nicht zwingend.
|
||||
4. **Die Auswahl „letzte drei je Repo" ist grob.** Bei `axion-backup` sind das
|
||||
beide Tags, auch der ungenutzte `v1` — der prompt CRITICAL meldet.
|
||||
|
||||
> **STOP — Freigabe für Gate 5.**
|
||||
|
||||
@@ -6,7 +6,7 @@ created: 2026-08-14
|
||||
milestone: M5
|
||||
priority: high
|
||||
area: security
|
||||
related: [docs/issues/0025-deploy-uebergabe-cve-alarme-aggregiert-receiver.md, docs/aar/2026-08-01-cve-pipeline-gitops47.md]
|
||||
related: [docs/issues/0025-deploy-uebergabe-cve-alarme-aggregiert-receiver.md, docs/aar/2026-08-01-cve-pipeline-gitops47.md, docs/issues/0106-cve-scan-deckt-nur-die-haelfte-zielliste-ist-handgepflegt.md]
|
||||
gitlab_iid: "37"
|
||||
---
|
||||
# CVE-Remediation-Pass: Schwachstellen-Report abarbeiten
|
||||
@@ -32,3 +32,32 @@ nur die Alarm-Zustellung ab — die eigentliche **Behebung** fehlte als eigenes
|
||||
|
||||
Hängt eng an der Update-Kadenz (#0052) — Updaten ist der Haupthebel gegen die CVEs.
|
||||
Methode/Runbook: `/betrieb/sicherheit`; Update-Prozess: `/betrieb/upgrades`.
|
||||
|
||||
## ⚠️ Die Grundmenge hat sich am 2026-08-21 verdoppelt (#0106)
|
||||
|
||||
Bis dahin beruhte jede Zahl in diesem Issue auf **29** gescannten Images. Das
|
||||
waren 52 % des Bestands — die Zielliste war handgepflegt und lief dem Ausrollen
|
||||
hinterher. Seit #0106 wird sie abgeleitet, es sind **56** Ziele, und die Deckung
|
||||
liegt bei 100 %.
|
||||
|
||||
| | vorher (29 Images) | jetzt (56 Images) |
|
||||
|---|---|---|
|
||||
| CRITICAL | 126 | **253** |
|
||||
| HIGH | 1222 | **3674** |
|
||||
|
||||
**Der Zuwachs ist kein neuer Schaden, sondern vorher unsichtbarer Bestand.**
|
||||
Erstmals überhaupt erfasst und sofort mit CRITICAL-Befunden dabei: das
|
||||
öffentlich erreichbare Wiki, **beide** Traefik-Schichten (Cluster und
|
||||
Betriebs-Host), Prometheus, Grafana, Alertmanager, die k3s-Bausteine, mehrere
|
||||
Postgres-Fassungen — und `aquasec/trivy` selbst.
|
||||
|
||||
Für diesen Remediation-Pass heißt das: Der Umfang ist rund doppelt so groß wie
|
||||
bei der Erstellung dieses Issues angenommen, und die neu hinzugekommenen
|
||||
Kandidaten sind die exponierteren (Ingress, öffentliches Wiki), nicht die
|
||||
harmloseren.
|
||||
|
||||
⚠️ **Ein Kandidat ist vermutlich gar nicht zu beheben, sondern zu entfernen:**
|
||||
`rohana.axion1337.de/sorb/axion-backup:v1` meldet CRITICAL, wird aber nirgends
|
||||
betrieben — er steht nur in der Zielmenge, weil die Registry-Auswahl „die
|
||||
letzten drei Fassungen je Repo" nimmt und das Repo nur zwei hat. Ein
|
||||
Registry-Aufräumen wäre hier die billigere Antwort als ein Update.
|
||||
|
||||
@@ -23,6 +23,8 @@ related:
|
||||
|
||||
| slice | commit | status | note |
|
||||
|---|---|---|---|
|
||||
| 5 | | | OPEN | Wartet auf Freigabe; Ausrollen durch sorb, Deckung 100 % bei 56 Zielen gemessen. |
|
||||
| 5 | (slice) | DONE | cAdvisor gegen docker compose config: Luecke 0, und vier Images gesehen, die in keinem Compose stehen. |
|
||||
| 4 | 07875ba | DONE | Berichte ausgefallener Ziele entfernt; Test laedt die echte runde() statt sie nachzubauen. |
|
||||
| 3 | 386b65a | DONE | Scanner liest die abgeleitete Menge, images.txt geloescht (Kriterium 1). |
|
||||
| 2 | b989987 | DONE | Vier Regeln statt drei; erste Regeltests im Stack, zwei Sabotagen belegen sie. |
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
type: wiki-page
|
||||
area: stolpersteine
|
||||
status: open
|
||||
sources:
|
||||
- "docs/design/2026-08-21-cve-ziele-ableiten.md"
|
||||
related:
|
||||
- "docs/wiki/stolpersteine/meldet-erfolg-ist-aber-blind.md"
|
||||
- "docs/wiki/stolpersteine/fremder-defekt-der-aenderung-angelastet.md"
|
||||
---
|
||||
|
||||
# Ein nachgebauter Test prüft den Nachbau
|
||||
|
||||
> **Stand: offen.** Noch nicht an das Rahmenwerk übergeben.
|
||||
|
||||
Ein Test, der die zu prüfende Logik **nachbildet**, statt sie zu **laden**,
|
||||
prüft die Nachbildung. Er kann dauerhaft grün bleiben, während die ausgelieferte
|
||||
Datei kaputt ist — und er sieht dabei genauso aus wie ein richtiger Test:
|
||||
gleiche Zusicherungen, gleiche Ausgabe, gleiche Zeile im Abschlussbericht.
|
||||
|
||||
Das ist der Sonderfall von [[meldet-erfolg-ist-aber-blind]], der die Prüfung
|
||||
selbst trifft statt das Geprüfte.
|
||||
|
||||
## Das belegte Vorkommen
|
||||
|
||||
Am **2026-08-21** entstand ein Shell-Test für eine Scan-Schleife. Die Schleife
|
||||
lebt in einer Datei, die als Endlosschleife startet und deshalb nicht ohne
|
||||
Weiteres geladen werden kann. Der bequeme Ausweg: den Rumpf im Test noch einmal
|
||||
hinschreiben, „wortgleich zur echten Datei".
|
||||
|
||||
**Dreizehn Zusicherungen liefen grün.** Darunter der wichtigste Fall überhaupt —
|
||||
*„ohne Zielliste darf nichts gelöscht werden"*, dessen Verletzung den gesamten
|
||||
Berichtsbestand vernichtet hätte.
|
||||
|
||||
Beim Umbau auf *Laden* der echten Funktion (Ladewache über eine
|
||||
Umgebungsvariable, die den Treiber am Anlaufen hindert) fielen **binnen eines
|
||||
einzigen Laufs** fünf Zusicherungen um: Die ausgelieferte Datei setzte ihre
|
||||
Pfade **fest** und ignorierte die Umgebung vollständig. Der Nachbau tat es
|
||||
nicht, weil im Nachbau die Pfade aus dem Test kamen.
|
||||
|
||||
Der Unterschied war also nicht theoretisch. Er war schon da, und der grüne Test
|
||||
hat ihn zugedeckt.
|
||||
|
||||
## Warum es so verführerisch ist
|
||||
|
||||
Der Nachbau ist fast immer der **kürzere** Weg, und er sieht aus wie
|
||||
Sorgfalt — schließlich steht die Logik zweimal da, einmal geprüft. Drei
|
||||
Umstände machen ihn attraktiv:
|
||||
|
||||
1. **Die Datei lässt sich nicht laden**, weil sie beim Laden etwas tut
|
||||
(Endlosschleife, Serverstart, Seiteneffekt beim Import).
|
||||
2. **Der Nachbau ist beim Schreiben korrekt.** Er entsteht ja aus derselben
|
||||
Vorlage; die Abweichung entsteht erst später, oder — wie hier — sie war von
|
||||
Anfang an da und fiel nicht auf, weil beide Fassungen nebeneinander gelesen
|
||||
und für gleich gehalten wurden.
|
||||
3. **Der Test wird nie wieder angefasst.** Die geprüfte Datei ändert sich, der
|
||||
Nachbau nicht. Ab da prüft er eine Vergangenheit.
|
||||
|
||||
## Was stattdessen zu tun ist
|
||||
|
||||
**Die zu prüfende Einheit muss ladbar sein, und der Test muss sie laden.**
|
||||
|
||||
Ist die Datei nicht ladbar, ist *das* der zu behebende Mangel — nicht ein
|
||||
Grund für einen Nachbau. Die übliche Lösung kostet drei Zeilen:
|
||||
|
||||
- Den Rumpf in eine **Funktion** heben und den Treiber ans Ende stellen.
|
||||
- Eine **Ladewache** davor, die den Treiber überspringt, wenn eine
|
||||
Umgebungsvariable gesetzt ist (`[ "${X_NUR_LADEN:-0}" = "1" ] && return 0`).
|
||||
In Python ist das `if __name__ == "__main__":` — dieselbe Sache, nur
|
||||
eingebaut.
|
||||
- Die Berührpunkte nach außen (Netzaufruf, Unterprozess) als **eigene
|
||||
Funktionen**, die der Test überschreibt. Nicht die Logik nachbauen, nur die
|
||||
Ränder ersetzen.
|
||||
|
||||
**Die Gegenprobe, die den Unterschied sichtbar macht:** Die geprüfte Datei
|
||||
absichtlich kaputt machen und schauen, ob der Test rot wird. Ein Test, der eine
|
||||
Nachbildung prüft, bleibt dabei **grün** — und genau daran erkennt man ihn.
|
||||
|
||||
## Warum das nicht plattformspezifisch ist
|
||||
|
||||
Die Umstände sind allgemein: eine Datei, die beim Laden etwas tut, und ein Test,
|
||||
der es bequem haben will. Das trifft Shell-Skripte mit Endlosschleife,
|
||||
Dienste, die beim Import lauschen, Konfigurationen, die im Test „nachgebildet"
|
||||
statt eingelesen werden, und jede Prüfung, die eine Regel im Testcode noch
|
||||
einmal formuliert, statt die ausgelieferte Regel zu laden.
|
||||
|
||||
Erntewürdig ist die Regel dahinter: **Wenn ein Test die zu prüfende Einheit
|
||||
nicht laden kann, ist die Einheit zu ändern — nicht der Test zu erfinden.**
|
||||
Reference in New Issue
Block a user