Files
threadnet-operating/monitoring/cve/test_scan_loop.sh
T
Thore Cimbal 07875ba6bb cve: a target that leaves the set loses its report (#0106)
Without this, an image dropped from the desired set keeps reporting: the
exporter reads every json in the results directory and takes the target from
Trivy's own ArtifactName. That is precisely what the security room showed on
2026-08-20, when it carried HIGH findings for threadnet-web:v0.3.0, an image
that runs nowhere.

Deletion only ever runs against a list that was successfully read. The guard at
the top of the round already skips everything when the list is missing or
empty, so a restart during a Prometheus outage cannot clear the estate.

The round became a function so the test can load the real one. The first version
of that test rebuilt the loop instead, and a rebuilt test proves the rebuild —
it stayed green while the shipped file set its paths unconditionally and ignored
the environment entirely. Loading it exposed that within one run.

Two of my own errors are fixed here as well. The driver read
`runde || sleep A && sleep B`, which groups left to right, so a missing list
would have slept the wait AND the full day — exactly what the short wait exists
to prevent. And the paths were hardcoded where the exporter already took them
from the environment.

Removing the guard as a deliberate sabotage turns the dangerous case red:
untouched reports drop from two to zero.
2026-08-21 12:00:00 +00:00

90 lines
3.5 KiB
Bash

#!/bin/sh
# Zusicherungen fuer scan-loop.sh (#0106), ohne Trivy und ohne Netz:
# sh cve/test_scan_loop.sh
#
# Gate 3 hatte diese Faelle bei test_targets.py vorgesehen. Sie stehen hier,
# weil die Logik in POSIX-Shell lebt - ein Python-Test haette sie nachgebaut
# statt geprueft, und ein nachgebauter Test prueft den Nachbau.
#
# ⚠️ Der gefaehrliche Fall ist nicht "loescht zu wenig", sondern "loescht zu
# viel": Ohne Zielliste darf NICHTS entfernt werden, sonst raeumt ein Neustart
# waehrend eines Prometheus-Ausfalls den gesamten Bestand ab. Deshalb steht zu
# jeder Zusicherung ihre Gegenprobe.
set -u
FEHLER=0
ARBEIT=$(mktemp -d)
trap 'rm -rf "$ARBEIT"' EXIT
pruefe() { # name erwartet ist
if [ "$2" = "$3" ]; then
echo " ok $1"
else
echo " FEHL $1 — erwartet '$2', bekommen '$3'"
FEHLER=$((FEHLER + 1))
fi
}
# ⚠️ Die ECHTE runde() wird geladen, nicht nachgebaut. Ein nachgebauter Test
# prueft den Nachbau: er kann gruen bleiben, waehrend die ausgelieferte Datei
# kaputt ist. Die Ladewache in scan-loop.sh verhindert, dass dabei die
# Endlosschleife anlaeuft.
ARBEIT_RESULTS="$ARBEIT/results"
ARBEIT_TARGETS="$ARBEIT/targets.txt"
RESULTS_DIR="$ARBEIT_RESULTS"
TARGETS_FILE="$ARBEIT_TARGETS"
SCAN_LOOP_NUR_LADEN=1
export RESULTS_DIR TARGETS_FILE
. "$(dirname "$0")/scan-loop.sh"
# Trivy durch eine Attrappe ersetzen - die einzige Stelle, die Netz braeuchte.
scanne() { : > "$2"; }
# Aus der ECHTEN Ausgabe lesen, statt eine eigene Form zu erwarten.
entfernt_aus() { echo "$1" | sed -n "s/.*, \([0-9]*\) verwaiste.*/\1/p" | tail -1; }
ausgesetzt_aus() { echo "$1" | grep -q "^warte:" && echo AUSGESETZT || echo GELAUFEN; }
mkdir -p "$ARBEIT_RESULTS"
echo "1) Ohne Zielliste wird NICHTS geloescht (der gefaehrliche Fall)"
: > "$ARBEIT/results/altbestand.json"
: > "$ARBEIT/results/zweiter.json"
rm -f "$ARBEIT_TARGETS"
ergebnis=$(runde)
pruefe "Runde ausgesetzt" "AUSGESETZT" "$(ausgesetzt_aus "$ergebnis")"
pruefe "Berichte unberuehrt" "2" "$(ls -1 "$ARBEIT_RESULTS" | wc -l | tr -d ' ')"
echo "2) Leere Zielliste ebenso"
: > "$ARBEIT_TARGETS"
ergebnis=$(runde)
pruefe "Runde ausgesetzt" "AUSGESETZT" "$(ausgesetzt_aus "$ergebnis")"
pruefe "Berichte unberuehrt" "2" "$(ls -1 "$ARBEIT_RESULTS" | wc -l | tr -d ' ')"
echo "3) Mit Zielliste: verwaiste Berichte verschwinden, neue entstehen"
printf '# Kopfzeile\npostgres:17-alpine\nrohana.axion1337.de/sorb/threadnet-web:v0.6.0\n' > "$ARBEIT_TARGETS"
ergebnis=$(runde)
pruefe "zwei verwaiste entfernt" "2" "$(entfernt_aus "$ergebnis")"
pruefe "genau die zwei Ziele bleiben" "2" "$(ls -1 "$ARBEIT_RESULTS" | wc -l | tr -d ' ')"
pruefe "altbestand ist fort" "" "$(ls "$ARBEIT_RESULTS" | grep altbestand || true)"
pruefe "v0.6.0 ist da" "rohana.axion1337.de_sorb_threadnet-web_v0.6.0.json" \
"$(ls "$ARBEIT_RESULTS" | grep v0.6.0)"
echo "4) Der Fall vom 2026-08-20: v0.3.0 faellt aus der Menge und verstummt"
: > "$ARBEIT/results/rohana.axion1337.de_sorb_threadnet-web_v0.3.0.json"
ergebnis=$(runde)
pruefe "v0.3.0 entfernt" "1" "$(entfernt_aus "$ergebnis")"
pruefe "v0.3.0 ist fort" "" "$(ls "$ARBEIT_RESULTS" | grep 'v0.3.0' || true)"
echo "5) Host-Scans werden nicht nach dieser Liste beurteilt"
: > "$ARBEIT/results/etwas__host__cfgmon.json"
ergebnis=$(runde)
pruefe "nichts entfernt" "0" "$(entfernt_aus "$ergebnis")"
pruefe "Host-Bericht bleibt" "etwas__host__cfgmon.json" \
"$(ls "$ARBEIT_RESULTS" | grep __host__)"
echo
if [ "$FEHLER" -eq 0 ]; then
echo "ALLE ZUSICHERUNGEN GRUEN"
else
echo "$FEHLER ZUSICHERUNG(EN) ROT"
fi
exit "$FEHLER"