0b7163e5af
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
50 lines
3.2 KiB
Markdown
50 lines
3.2 KiB
Markdown
# 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=../../../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/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.
|