mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-08 01:09:46 -10:00
Address #112 re-review: fail rewind on undecodable checkpoint payload
[major] restoreToSequenceLocked silently continued past a checkpoint whose payload failed to decode, which let a NEWER checkpoint win for that path and restore a later state than the caller requested. Treat corruption as a hard rewind error. Regression test TestRestoreFailsOnCorruptCheckpointPayload (fails-open before, errors now).
Re-review note: the earlier capture-target-validation (critical) and test bounds (minor) were already addressed in 600337b (resolveWithinWorkspace guard + len checks); those re-anchored threads are stale. The restore-side symlink-swap TOCTOU remains a documented heavy-lift residual (needs openat2/descriptor traversal), tracked for the rewind-wiring work.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
1 parent
600337bf70
commit
24e9ba2299
2 files changed
+15
-1
No files matched your search
@@ -256,6 +256,17 @@ func TestRestoreRejectsPathTraversal(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreFailsOnCorruptCheckpointPayload(t *testing.T) {
|
||||
store, ws := newCkStore(t)
|
||||
target := mustAppend(t, store, AppendEventInput{Type: EventMessage, Payload: map[string]any{}})
|
||||
// A checkpoint event whose payload does not decode into CheckpointPayload must
|
||||
// abort the rewind, not be silently skipped (which could restore a later state).
|
||||
mustAppend(t, store, AppendEventInput{Type: EventSessionCheckpoint, Payload: "corrupt-not-a-checkpoint"})
|
||||
if _, err := store.RestoreToSequence("s", ws, target.Sequence); err == nil {
|
||||
t.Fatal("expected rewind to fail on undecodable checkpoint payload")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncateToZeroProducesEmptyFile(t *testing.T) {
|
||||
store, _ := newCkStore(t)
|
||||
mustAppend(t, store, AppendEventInput{Type: EventMessage, Payload: map[string]any{}})
|
||||
|
||||
@@ -56,7 +56,10 @@ func (store *Store) restoreToSequenceLocked(sessionID, workspaceRoot string, tar
|
||||
ev := checkpoints[i]
|
||||
var payload CheckpointPayload
|
||||
if err := json.Unmarshal(ev.Payload, &payload); err != nil {
|
||||
continue
|
||||
// Fail the rewind rather than skipping: silently continuing would let a
|
||||
// NEWER checkpoint win for that path and restore a later state than the
|
||||
// caller asked for. Corruption is a hard error.
|
||||
return report, fmt.Errorf("decode checkpoint payload seq %d: %w", ev.Sequence, err)
|
||||
}
|
||||
for _, f := range payload.Files {
|
||||
// Process only the CLOSEST-to-target entry per path. We iterate
|
||||
|
||||
Reference in new issue
Block a user