v0.76.0: campaign-#3 hardening (settings .bak recovery, restore stack_name validation, quiesce marker quarantine)

S1: corrupt settings.json recovers from .bak / safe-defaults+preserve, no crash-loop.
F2: validStackName gates restore + export handlers (reject /,\,..,NUL traversal).
S3: corrupt quiesce marker logged + quarantined, not silently dropped.
Tests T-S1/F2/S3 + red-proofs. Agent/hub untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 23:34:33 +02:00
parent 141d51bf19
commit b0dd13154b
10 changed files with 305 additions and 2 deletions
+21
View File
@@ -1,5 +1,26 @@
## Changelog
### v0.76.0 — campaign-#3 hardening: settings recovery, restore-name validation, quiesce-marker quarantine (2026-06-22)
Three controller findings from chaos campaign #3, all small, all controller-side.
- **S1 [MEDIUM] — no more crash-loop on a corrupt `settings.json`.** `internal/settings/settings.go`:
`save()` now writes a last-known-good `<path>.bak` **after** the primary rename succeeds (best-effort);
`Load()` on a JSON-parse error recovers from `.bak` (re-promotes it to primary) and, failing that,
**preserves** the corrupt file as `*.corrupt-<ts>` and starts on safe defaults — never returns the
error that made `main.go` `Fatalf`/crash-loop. New `Settings.LoadWarning` surfaced as a dashboard
banner. (`main.go`'s `Fatalf` stays — now only the genuine IO-unreadable path is fatal.) Recovery is
safe: an empty `PasswordHash` falls back to `controller.yaml`, the storage registry re-discovers.
- **F2 [MEDIUM, defense-in-depth] — validate `stack_name` against path traversal.** New
`web/validate.go` `validStackName` (single segment; rejects `/`, `\`, `..`, NUL). Gated in
`backupRestoreHandler` (`handlers.go`) and `apiExportStart` (`handler_export.go`) before any
restore/export work. (Storage `where=` was already validated by `gateWhere`.)
- **S3 [LOW] — quarantine a corrupt quiesce marker.** `quiesce/quiesce.go` `readMarker` now logs a
`[WARN]` + renames a bad-JSON marker to `*.corrupt-<ts>` instead of silently dropping it (still
returns "no marker" → no recovery, the correct contract).
- Tests: T-S1a-d (settings recovery), T-F2a-c (validation + both handlers), T-S3a/b (quarantine), all
red-proofed against the pre-fix code. Agent/hub untouched.
### v0.75.0 — gate userdata MkdirAll on a live mountpoint (no writes into an absent drive) (2026-06-22)
**Bugfix — two `MkdirAll`-into-`<drive>/userdata` sites fired without checking the drive was mounted**,