Commit f7be0c4
fix(v1.100 PR-26-code-C2): A.4 corrupt-manifest is hard refusal, not soft-skip (auditor verdict)
Auditor focused-audit on PR-26-code-C flagged a semantic risk in
the A.4 corrupt-manifest branch: previously a corrupt /
hash-mismatch / unknown-entry / parse-failure manifest was a
soft-skip with an informational sentinel, and A.5 still ran. The
auditor argued — correctly — that proceeding to start csf.service
when restore evidence is on disk but cannot be trusted weakens the
evidence chain.
Locked rule (per auditor verdict):
manifest absent → soft-skip warning, continue to A.5 [migration gap, kept]
manifest incomplete → ErrCSFRestoreCronManifestCorrupt, stop before A.5
hash mismatch → ErrCSFRestoreCronManifestCorrupt, stop before A.5
target exists dirty → ErrCSFRestoreCronTargetExists, stop before A.5
manifest clean → restore exact files, then continue to A.5
Behavior delta (this commit only — C1 + C2 + CI gate semantics
remain otherwise unchanged):
- Manifest parse failure / schema mismatch / unknown-entry path
→ A.4 returns wrapped ErrCSFRestoreCronManifestCorrupt; A.5 does
NOT run; the existing §32 step-3 failure path retains the safety
net.
- Per-entry sha256 mismatch → same hard refusal.
- Operator-content collision (target /etc/cron.d/<name> already
exists) → A.4 returns wrapped ErrCSFRestoreCronTargetExists; A.5
does NOT run.
- Manifest absent (pre-PR-26 host) → unchanged: graceful soft-skip
with operator warning, control falls through to A.5.
- Manifest clean → unchanged: restore both files, fall through to
A.5.
Files changed:
cmd/nftban-installer/restore_deps_csf.go
- ErrCSFRestoreCronManifestCorrupt docstring rewritten: now
documents hard-refusal semantics (was: informational soft-skip).
Wording updated: "refusing before A.5 (operator must inspect)".
- New typed sentinel ErrCSFRestoreCronTargetExists for the
operator-content-collision case. Distinct from
ErrCSFRestoreCronManifestCorrupt for cleaner classification: a
collision is an evidence conflict, not a manifest-trust failure.
- A.4 step rewritten:
* manifestErr branch now returns the wrapped sentinel instead
of falling through.
* Per-entry sha256 verify failure now returns instead of skip.
* Per-entry unauthorized-Path now returns instead of skip.
* Per-entry target-exists collision now returns
ErrCSFRestoreCronTargetExists instead of skip.
* Per-entry WriteFileAtomic failure now returns instead of skip.
* Chown failure remains soft (logged warning, content already
restored — partial-restore is recoverable; the integrity
chain is unaffected).
cmd/nftban-installer/restore_deps_csf_test.go
- Renamed + retargeted three tests to assert hard-refusal:
PR26C2_A4_TargetExists_SkipsRestore
→ PR26C2_A4_TargetExists_HardRefuses_StopsBeforeA5
+ asserts errors.Is(err, ErrCSFRestoreCronTargetExists)
+ asserts NOT mock.CommandCalled("systemctl","start",csf.service)
PR26C2_A4_SHA256Mismatch_SoftSkip_A5StillRuns
→ PR26C2_A4_SHA256Mismatch_HardRefuses_StopsBeforeA5
+ asserts errors.Is(err, ErrCSFRestoreCronManifestCorrupt)
+ asserts A.5 NOT called
PR26C2_A4_SchemaMismatch_SoftSkip_A5StillRuns
→ PR26C2_A4_SchemaMismatch_HardRefuses_StopsBeforeA5
+ asserts errors.Is(err, ErrCSFRestoreCronManifestCorrupt)
+ asserts A.5 NOT called
PR26C2_A4_UnknownEntryPath_Rejected
→ PR26C2_A4_UnknownEntryPath_HardRefuses_StopsBeforeA5
+ asserts errors.Is(err, ErrCSFRestoreCronManifestCorrupt)
+ asserts A.5 NOT called
- 3 new tests pinning the kept-behavior branches:
PR26C2_A4_HappyPath_ContinuesToA5 — clean restore continues
PR26C2_A4_ManifestAbsent_ContinuesToA5 — migration soft-skip continues
PR26C2_A4_ParseFailure_HardRefuses_StopsBeforeA5 — parse failure stops
Push criteria (all met as of this commit):
- manifest absent = migration soft-skip ✓ (test #10 above)
- manifest corrupt/hash mismatch = typed refusal before A.5 ✓ (tests #4, #5, #8, #11)
- target cron path broad writes = impossible ✓ (allow-list + writer scope)
- writer-before-reader invariant = tested ✓ (C1's roundtrip + C2's HappyPath_RestoresBothFiles)
- G4-RESTORE-CRON-MANIFEST-INTEGRITY = PASS (local replay clean)
- go test ./... + race + vet = PASS on lab2
Verified on lab2 (Ubuntu 24.04, go1.22.2):
- go build ./... clean
- go test ./... (full repo) PASS
- go test -race -count=1 cmd + restore + state + switchop PASS
- go vet ./... clean
- 11 TestCSFMutate_PR26C2_* tests all PASS (3 hard-refusal tests
retargeted; 1 unchanged; 7 unchanged or new)
- existing PR-25 / PR-26-code-A / PR-26-code-B tests all still pass
- G4-RESTORE-CRON-MANIFEST-INTEGRITY local replay: FAIL=0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 93e86e2 commit f7be0c4
2 files changed
Lines changed: 182 additions & 87 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
144 | | - | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
151 | | - | |
152 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
153 | 171 | | |
154 | 172 | | |
155 | 173 | | |
| |||
324 | 342 | | |
325 | 343 | | |
326 | 344 | | |
327 | | - | |
| 345 | + | |
| 346 | + | |
328 | 347 | | |
329 | | - | |
| 348 | + | |
330 | 349 | | |
331 | | - | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
342 | | - | |
343 | | - | |
344 | | - | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
345 | 370 | | |
346 | 371 | | |
347 | 372 | | |
348 | 373 | | |
349 | 374 | | |
350 | 375 | | |
351 | 376 | | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | | - | |
357 | | - | |
358 | | - | |
359 | 377 | | |
360 | 378 | | |
361 | 379 | | |
362 | | - | |
363 | 380 | | |
364 | | - | |
| 381 | + | |
365 | 382 | | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
| 383 | + | |
373 | 384 | | |
374 | 385 | | |
375 | | - | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
376 | 390 | | |
377 | 391 | | |
378 | 392 | | |
379 | | - | |
380 | 393 | | |
381 | 394 | | |
| 395 | + | |
| 396 | + | |
382 | 397 | | |
383 | 398 | | |
384 | | - | |
| 399 | + | |
385 | 400 | | |
386 | | - | |
| 401 | + | |
387 | 402 | | |
388 | 403 | | |
| 404 | + | |
| 405 | + | |
389 | 406 | | |
390 | 407 | | |
391 | 408 | | |
392 | | - | |
| 409 | + | |
393 | 410 | | |
394 | | - | |
395 | | - | |
| 411 | + | |
396 | 412 | | |
397 | | - | |
398 | | - | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
399 | 416 | | |
400 | 417 | | |
401 | | - | |
| 418 | + | |
402 | 419 | | |
403 | | - | |
404 | | - | |
| 420 | + | |
405 | 421 | | |
406 | 422 | | |
407 | 423 | | |
408 | 424 | | |
409 | 425 | | |
410 | | - | |
| 426 | + | |
411 | 427 | | |
412 | | - | |
413 | | - | |
| 428 | + | |
414 | 429 | | |
415 | 430 | | |
416 | 431 | | |
| |||
425 | 440 | | |
426 | 441 | | |
427 | 442 | | |
428 | | - | |
429 | | - | |
| 443 | + | |
430 | 444 | | |
431 | 445 | | |
432 | 446 | | |
| |||
0 commit comments