fix(provision): corrupt-journal undo quarantine, honestly scoped (U10 U5 follow-up) - #54
Conversation
…nother project (U10 U5 follow-up) The PR #52 Copilot fold trimmed conflicting rows from a batch but the post-PR gate caught it was incomplete: newestBatchForProject still resolved a batchId from the raw journal, so a corrupted project-B row reusing project-A's batchId made default undo(A or B) select A's batch and delete/restore A's REAL files. Complete fix: groupBatches QUARANTINES a batchId that appears under two canonical roots (drops the whole batch, not just the conflicting rows), and newestBatchForProject selects only from that validated grouping by the batch's own single canonical root — so a poisoned batchId is reachable by neither default nor explicit undo. Adds a filesystem-level regression proving A's real file survives a corrupt B-row reusing its batchId. 622 tests green, tsc clean.
… a security boundary (decision #45) The post-PR gate escalated the corrupt-journal follow-up into a full adversarial-journal-integrity surface (duplicate entry-ids, projectRoot not constraining targetPath, quarantined-newest fallback). All of it is reachable ONLY by writing semantically-valid malicious entries into the 0700 data dir = attacker-owns-HOME = out of decision #45. On any legitimate journal nothing is ever quarantined (unique randomUUID batchId, one canonical root, contained targetPath — all by construction), so the guard is a no-op there. The real defect was over-claiming comments: #52 and this branch read as 'fail-closed cross-project safety' they don't fully deliver. Fix the CLAIM, not the behavior — keep the harmless best-effort quarantine, document it honestly as corruption-tolerance defense-in-depth, and defer full journal-integrity hardening to issue #53 (triggered only if the threat model expands to a hostile data dir). 622 tests green, tsc clean.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe provisioning journal now quarantines batch IDs reused across canonical project roots. Newest-batch lookup uses validated grouped batches, and tests verify that corrupted cross-root entries cannot trigger undo or file deletion. ChangesBatch quarantine
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens the provisioning undo batch reader against a corrupted undo journal by quarantining any batchId that appears under multiple canonical project roots, ensuring default/explicit undo never selects a cross-root batch.
Changes:
- Update
groupBatchesto quarantine (drop) an entirebatchIdif it is observed under multiple canonicalizedprojectRootvalues. - Update
newestBatchForProjectto select the newest batch from the already-validated grouping (so quarantined batches are never candidates). - Expand regression tests to assert quarantining behavior and to ensure a poisoned cross-root
batchIdcannot cause another project’s files to be reversed.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| tests/provision-apply.test.ts | Replaces the prior “drop conflicting row” expectation with “quarantine whole batch” and adds an integration regression asserting poisoned batch IDs do not delete real files. |
| src/provision/runs.ts | Implements batch quarantine on cross-root reuse and adjusts newest-batch selection to operate only on non-quarantined grouped batches. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/provision/runs.ts`:
- Around line 81-88: The batch selection in the flow using groupBatches must
preserve journal recency: after applying the existing quarantine validation and
projectRoot filter, select the batch associated with the last qualifying journal
entry rather than the last group ordered by first appearance. Update the
relevant selection logic around groupBatches and add a regression case covering
A1, B1, A2, which must select A.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 7f5d74f5-e332-44ca-8152-14d9b5190e12
📒 Files selected for processing (2)
src/provision/runs.tstests/provision-apply.test.ts
…earance order (CodeRabbit #54) My newestBatchForProject rewrite selected the last batch in groupBatches first-appearance order, which diverges from the documented 'batch of the LAST-APPEARING journal entry' under interleaved entries (A1,B1,A2 → should be A, was B). Resolve each journal entry's batchId (newest-first) through the VALIDATED grouping so a quarantined batchId is never a candidate and only a root-matching batch is returned — restoring the documented recency while keeping the cross-root quarantine. Adds the A1,B1,A2 recency regression. 623 tests green, tsc clean.
U10 U5 follow-up — corrupt-journal undo quarantine (best-effort, honestly scoped)
Follow-up to #52, from a CodeRabbit/Copilot comment about batch grouping on an untrusted undo journal.
What this does
groupBatchesquarantines abatchIdthat appears under two different canonical project roots (drops the whole batch, not just conflicting rows), andnewestBatchForProjectselects only from that validated grouping. Best-effort defense-in-depth against a corrupted journal.readBatchesquarantine test.Threat-model scope (application of decision #45 — flagged for veto, not a new decision)
The Codex gate escalated this into a full adversarial-journal-integrity surface (duplicate entry-ids redirecting
undo, self-declaredprojectRootnot constrainingtargetPath, quarantined-newest fallback, ABA-under-quarantine). All of it requires writing semantically-valid malicious entries into the0700data dir — i.e. an attacker who already owns the user's home dir and can rewrite the real configs directly. That is decision #45's explicit attacker-owns-HOME, out-of-scope boundary.On any legitimate journal none of it can occur, by construction: each
apply()stamps a freshrandomUUIDbatchId under one ingress-canonicalized root, entry ids are per-writerandomUUIDs, andtargetPath = posix.join(projectRoot, PortableRelPath)is a contained descendant. So the quarantine is a no-op on real journals — the fix's real content is correcting over-claiming comments (#52 and an earlier version of this branch read as "fail-closed cross-project safety" they don't fully deliver) to say honestly: corruption-tolerance defense-in-depth, not a security boundary.Full journal-integrity hardening is deferred to #53, triggered only if the threat model ever expands to a hostile/shared data dir. The Codex gate approved this framing.
622 tests green, tsc clean.
Summary by CodeRabbit