0b7163e5af
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.2 KiB
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.jsonno longer crash-loops the controller.internal/settings/settings.go:save()writes a last-known-good<path>.bakafter 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 drovemain.go'sFatalf/crash-loop. NewSettings.LoadWarning→ dashboard banner (handlers.go+dashboard.html).main.goFatalfretained (now only the IO-unreadable path). - F2 [MEDIUM, defense-in-depth] —
stack_namevalidated against path traversal.internal/web/validate.govalidStackName(single segment; rejects/ \ .. NUL), gated inbackupRestoreHandler(handlers.go) andapiExportStart(handler_export.go) before any restore/export work. - S3 [LOW] — corrupt quiesce marker quarantined, not silently dropped.
internal/quiesce/quiesce.goreadMarkerlogs 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
Loadto 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
readMarkerto 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
- S1: ensured
.bak, truncatedsettings.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. - F2:
POST /backup/restore stack_name=../../../etc→flash_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/passwdintact (24 lines). - 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.