Files
felhom-controller/REPORT.md
T
2026-06-22 23:39:20 +02:00

3.2 KiB

REPORT — controller v0.76.0: campaign-#3 hardening (3 fixes)

Baseline → target

felhom-controller main v0.75.0 → v0.76.0. Controller-only; agent/hub untouched.

What changed (commit b0dd131)

Three findings from chaos campaign #3, severity-ordered:

  • S1 [MEDIUM] — corrupt settings.json no longer crash-loops the controller. internal/settings/settings.go: save() writes a last-known-good <path>.bak after the primary rename succeeds (best-effort). Load() on a parse error recovers from .bak (re-promotes to primary), else preserves the corrupt file as *.corrupt-<ts> and starts on safe defaults — never returns the error that drove main.go's Fatalf/crash-loop. New Settings.LoadWarning → dashboard banner (handlers.go + dashboard.html). main.go Fatalf retained (now only the IO-unreadable path).
  • F2 [MEDIUM, defense-in-depth] — stack_name validated against path traversal. internal/web/validate.go validStackName (single segment; rejects / \ .. NUL), gated in backupRestoreHandler (handlers.go) and apiExportStart (handler_export.go) before any restore/export work.
  • S3 [LOW] — corrupt quiesce marker quarantined, not silently dropped. internal/quiesce/quiesce.go readMarker logs a [WARN] + renames a bad-JSON marker to *.corrupt-<ts> (still returns "no marker" → no recovery, the correct contract).

Tests (green; all red-proofed)

go build ./... && go vet ./... && go test ./... — green.

  • T-S1a recover-from-bak, T-S1b unrecoverable→safe-defaults+preserve, T-S1c save-writes-bak, T-S1d happy. Red-proof: revert Load to error-on-parse → T-S1a/b FAIL.
  • T-F2a validStackName (pure), T-F2b restore handler rejects traversal (no nil-deref), T-F2c export handler → 400. Red-proof: drop the restore gate → traversal falls through to the nil-backupMgr redirect → T-F2b FAIL.
  • T-S3a quarantine+log, T-S3b happy. Red-proof: revert readMarker to silent → T-S3a FAIL.

Deploy

Built+pushed :0.76.0 on 192.168.0.180 (build-server git pull done first), deployed to 9201 via bootstrap. docker inspect: image=:0.76.0 running healthy.

LIVE re-test — the three campaign-#3 scenarios, now fixed

  1. S1: ensured .bak, truncated settings.json → restart → status=running RestartCount=0 (no crash-loop), logs [ERROR] primary settings corrupt … — attempting recovery from .bak[WARN] recovered settings from .bak; re-promoting to primary; config intact (3 storage_paths), 25/0.
  2. F2: POST /backup/restore stack_name=../../../etcflash_error=Érvénytelen alkalmazásnév, log [WARN] restore rejected: invalid stack_name "../../../etc", no "Starting app-data restore" (gate fired before the restore logic); /etc/passwd intact (24 lines).
  3. S3: planted a corrupt quiesce-state.json → restart → [WARN] [quiesce] marker … is corrupt … — quarantining; stacks not auto-recovered, original renamed to *.corrupt-<ts>, RestartCount=0, 25/0 (stacks not stranded).

Still-open campaign-#3 deferrals (NOT in this task)

Time-chaos on a dedicated VM / injectable clock; host-reboot (supervised); .fab import compose-fuzzing. Agent/hub untouched.