Update Cargo.toml with 22 changed files (#4470) - #4488
Conversation
Automatic checkpoint to preserve work in progress. Tests and implementation saved before refactoring phase.
…docs Step 9 refactor/simplify of the #4470/#4469 fix. Additive, non-breaking. - Delegate the quarantine-name predicate in self_deploy/health.rs and self_deploy/quarantine_ack.rs to the canonical crate::cmd_cleanup::is_corrupt_quarantine_name, removing two identical copies that the comments already warned had to be kept in sync (drift hazard eliminated; single source of truth). - Fix stale doc comments in quarantine_ack.rs: drop the obsolete "implementation is TODO" note (now implemented) and correct the ACK_MARKER_BYTES comment that mislabeled the payload as an upper bound. No behavior change. Build clean; clippy clean; self_deploy, cmd_cleanup, quarantine_ack, self_relaunch lib tests and self_deploy_convergence integration test all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion The #4469 change made `remove_old_corrupt_dbs` resolve its scan directory through `simard_state_root()` (honoring `SIMARD_STATE_ROOT`) instead of the hardcoded `$HOME/.simard`. That is correct for production — the sweep and the self-health `no_quarantine` probe must scan the same resolved root — but it coupled the cmd_cleanup sweep tests to the process-global `SIMARD_STATE_ROOT` env, which many lib tests mutate under unrelated serial keys. Under the full parallel suite a concurrent setter redirected the scan dir, so the sweep found nothing and four tests failed (aged/keep-last-N quarantines "not swept"). Decouple the sweep logic from state-root resolution: add path-injected `remove_old_corrupt_dbs_in(scan_dir, report)` and have the public `remove_old_corrupt_dbs` delegate to it with the resolved root. Drive all sweep tests through the injected variant against a tempdir, removing every `HOME`/`SIMARD_STATE_ROOT` mutation from them. The tests are now deterministic and free of cross-test env races. The `corrupt_db_sweep_scans_resolved_state_root` wiring test still exercises the public wrapper's env resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review
Scope reviewed: main...pr-4488 (17 files, +1924/−168). Implements #4469 (durable .ack quarantine acknowledgement to break the stuck-quarantine self‑deploy deadlock) plus #4470 (canary failing‑test diagnosability + hermetic adaptive_scaling test).
Verification performed: the local working tree is mid‑merge, so I built and ran the changed‑module tests against the clean committed tip in a detached worktree (reusing the cached target dir). All green:
- lib:
quarantine_ack10,corrupt_db13,aged_protected5,auto_ack4,quarantine_scan5,self_health6,extract_first_failure3,sanitize_gate_detail3 - integration:
self_deploy_convergence4/4,adaptive_scaling22/22
Overall this is high‑quality, well‑tested, well‑documented work. Two issues below; the first is blocking.
🔴 Blocking — does not merge cleanly with main, and a naive resolution regresses behavior
The branch conflicts with current main in src/cmd_cleanup/disk.rs, src/cmd_cleanup/tests.rs, src/self_deploy/health.rs, docs/concepts/reconcile-and-self-deploy.md, and docs/reference/self-deploy-api.md. This is not a mechanical conflict — both branches refactored the same function for the same issue (#4469):
mainrefactoredremove_old_corrupt_dbsto sweep two directories independently —simard_state_root()andresolve_subdir("state")— viareclaim_corrupt_dbs_in_dir(dir), with per‑directory keep‑N / recovery‑asset protection.- This PR refactored the same function into
scan_quarantine_candidates+remove_old_corrupt_dbs_in(scan_dir)but sweeps onlysimard_state_root().
Consequence: taking this PR's version wholesale would drop main's live‑store (state/) directory sweep — a regression. The same gap exists on the read side: this PR's count_quarantine_files and auto_ack_stuck_recovery_asset scan only state_root, so ack‑awareness / auto‑ack would not cover the state/ subdir that main now sweeps.
Required resolution (semantic, not mechanical): layer this PR's .ack‑aware scan_quarantine_candidates / sidecar‑reclaim / select_protected_asset / aged_protected_recovery_asset on top of main's per‑directory model — i.e. run the ack‑aware sweep for each directory main sweeps, and apply the probe/auto‑ack across the same directory set. Re‑run the changed‑module + integration tests after resolving. (The committed PR tip itself is clean and green; the break is purely against latest main.)
🟡 Minor — orphaned .ack sidecars are never reclaimed
reclaim_ack_sidecar removes a sidecar only when its parent quarantine is swept, and scan_quarantine_candidates excludes *.ack. If a quarantine is removed out‑of‑band (operator rm, or manual deletion of the #2550 protected asset), its .ack becomes a permanent orphan — 13 bytes each, but unbounded over time. corrupt_db_sweep_never_treats_ack_marker_as_quarantine shows this is currently by design. Consider a periodic reclaim of *.ack markers whose parent artifact no longer exists. Low priority.
🟢 Strengths (verified)
- Root‑cause fix via single‑sourcing.
is_corrupt_quarantine_name,select_protected_asset, andaged_protected_recovery_assetare now defined once and shared by the probe, sweep, and ack paths — this is precisely what eliminates the probe/sweep disagreement that caused the deadlock. The duplicatedis_corrupt_quarantine_nameinhealth.rsis correctly removed. - Strong symlink/TOCTOU defense in
acknowledge:symlink_metadatapre‑check +create_new(O_EXCL, never follows a symlink) + explicitAlreadyExistsrace handling + refusal to overwrite a non‑regular‑file sidecar. Covered byacknowledge_refuses_to_overwrite_planted_symlink_marker. - Path‑safety in
is_ackable_quarantine_basename: rejects separators,.., absolute paths, and marker names, verified against a singleComponent::Normalequal to the whole name. Thorough negative tests. - Correct freshness semantics: filename‑keyed markers keep new corruption failing the probe (
quarantine_scan_still_flags_fresh_corruption_after_ack); the guarded auto‑ack fires only when the asset is both ≥ the #2550 protection floor and past the forensic window (auto_ack_ignores_fresh_protected_asset,auto_ack_ignores_trivial_aged_quarantine), and is idempotent. - Non‑destructive by construction: the recovery asset and its marker are always retained (
corrupt_db_sweep_retains_protected_asset_marker). - Test hermeticity fixes: replacing process‑wide
HOMEmutation with the path‑injectedremove_old_corrupt_dbs_inremoves cross‑test env races;scaler: Noneinadaptive_scalingremoves theSIMARD_SCALINGenv dependency (#2732). These are genuine flakiness fixes, not test‑weakening. - #4470 diagnosability:
extract_first_failure+sanitize_gate_detailsurface the first failing test instead of an opaqueexit 101, with control‑char stripping and UTF‑8‑boundary‑safe byte bounding to prevent log/JSON forgery from untrusted subprocess output. The oldtruncate_outputand its tests are fully removed (no dead code). - Checklist: no TODOs / stubs / unimplemented functions; no swallowed exceptions (the
let _ = auto_ack_stuck_recovery_asset(...)is documented best‑effort and internally logs on error); structuredtracing/OTel only — noprint!/println!in the new paths.
Nits (non‑blocking)
sanitize_gate_detailtruncates without an ellipsis, unlike the removedtruncate_output— cosmetic.count_quarantine_filesdoes onesymlink_metadatastat per candidate viais_acknowledged; fine at keep‑N scale, just an O(n) stat note.
Verdict: Approve the design and implementation on their own merits, but must resolve the main conflict semantically (preserve the two‑directory sweep) and re‑validate before merge.
🤖 Automated Step 17b review. Build/test evidence gathered from a clean worktree at the PR tip.
rysweet
left a comment
There was a problem hiding this comment.
🔒 Step 17c — Security Review
Verdict: SECURE. No high-confidence exploitable vulnerabilities in the changed code. One Low-severity, same-trust-boundary log-forgery observation (advisory).
Validated against a clean checkout of the PR tip (the local tree is mid-merge). Every filesystem operation was traced against the actual code (not the doc-comments), and remove_dir_all's symlink behaviour was verified empirically.
Security checklist
- Security requirements met
- No new vulnerabilities (high-confidence)
- Sensitive-data handling reviewed
- Authorization / autonomous-action safety reviewed
- Injection vectors (path, symlink, log/JSON) reviewed
Findings by area
| # | Area | Result | Evidence |
|---|---|---|---|
| 1 | Path traversal in acknowledge/ack_marker_path |
✅ Sound | is_ackable_quarantine_basename (quarantine_ack.rs) layers separator rejection + single-Normal-component check + c == OsStr::new(name) equality; .join() can only produce a direct child of state_root. .., /etc/passwd, sub/x, x\evil, empty all rejected. |
| 2 | Symlink / TOCTOU on ack write | ✅ Sound | symlink_metadata (no-follow) → OpenOptions::create_new(true) (O_EXCL, no-follow). Planted symlink ⇒ AlreadyExists ⇒ re-lstat, Ok only for regular file. Covered by acknowledge_refuses_to_overwrite_planted_symlink_marker. No write-through. |
| 3 | Arbitrary deletion (reclaim_ack_sidecar, sweep) |
✅ Sound | Sidecar reclaim only remove_files a regular file; symlink/dir left untouched. Verified remove_dir_all on a symlink-to-dir unlinks the symlink only (target intact) — no recursive delete-through. Deletes gated by name match + age/keep caps + protected-asset skip. |
| 4 | Untrusted-output sanitization (#4470) | ✅ Sound at sinks (minor Unicode gap) | sanitize_gate_detail collapses every char::is_control() (CR/LF/NUL/TAB/ESC → space) and byte-bounds on a UTF-8 boundary (non-panicking). Gap: U+2028/U+2029 & bidi-override pass through, but the sink is tracing structured fields + JSON serialization, which escape safely ⇒ not exploitable (terminal-display spoofing only). |
| 5 | Autonomous auto-ack signal suppression (#4469) | ✅ Sound | auto_ack_stuck_recovery_asset acks only the single aged_protected_recovery_asset (largest ≥1 MiB, past the forensic window; future-dated mtime ⇒ unwrap_or_default()=0 ⇒ not aged). Filename-keyed ⇒ any fresh/unrelated cognitive*.corrupt-<ts> still counts and reddens the probe. Acked asset is retained, never deleted. No cross-file suppression. |
| 6 | Sensitive-data handling | .ack payload is the fixed constant b"acknowledged\n" — no secrets. SimardError Display drops path; unsafe-name reason uses escaped {:?}. But raw filename logged via %name (Display) — Finding 1. |
|
| 7 | DoS / resource | ✅ Nothing anomalous | Metadata-only reads; Vec growth bounded by dir entry count; size computed once per candidate. No unbounded file reads. |
| 8 | CLI parse_flags / env handling |
✅ Sound | Strict allowlist; --pre-deploy-facts= parsed as u64 with error on failure; state_root from trusted simard_state_root(). Every unsafe { set_var } is confined to #[cfg(test)]. |
Finding 1 — Unsanitized quarantine filename logged via %name (log-line forgery)
Severity: Low · Confidence: Medium — src/self_deploy/health.rs (auto_ack warn) and src/operator_cli/self_health.rs (acknowledge_quarantine_failed warn).
tracing::warn!(artifact = %name, …) logs the raw quarantine basename with Display. Unix filenames may contain newlines, and name only needs to satisfy is_corrupt_quarantine_name (cognitive./cognitive_memory. prefix + .corrupt- infix). Under the default non-JSON subscriber, field values are written raw, so a filename like cognitive.corrupt-1<LF>level=ERROR forged line can forge an additional log line. This is the same log-forgery class the PR's #4470 change already sanitizes for GateResult.detail — but that discipline isn't applied to quarantine filenames.
- Threat model: requires an attacker who can write to the daemon's
state_root(~/.simard) and make the file the aged ≥1 MiB protected asset — the same trust boundary the daemon already relies on. In JSON mode (.json()) the value is escaped and the issue does not arise. Impact is log spoofing / repudiation only — no data disclosure or code execution. Below the high-confidence bar; surfaced only because it's directly in scope. - Suggested fix (advisory, non-blocking): route the filename through the existing sanitizer before logging, e.g.
artifact = %sanitize_gate_detail(&name, GATE_DETAIL_MAX_BYTES), sohealth.rs/self_health.rsmatch the #4470 sanitization discipline.
Verified strengths
O_EXCL + no-follow ack write · symlink/TOCTOU-safe reclaim & probe · path-traversal rejection (separators, .., absolute, drive/root) · filename-keyed acks that never silence fresh corruption · non-destructive retention of the #2550 recovery asset · single-sourced protected-asset + age gate · #[cfg(test)]-confined env mutation · fixed constant .ack payload (no secrets) · strict allowlist CLI parsing.
No blocking security issues. The one Low finding is advisory and does not gate merge on its own. (Note: the separate code review flagged an unrelated blocking merge-conflict/semantic-overlap with main that must still be resolved.)
rysweet
left a comment
There was a problem hiding this comment.
🧭 Philosophy Guardian Review — PR #4488
Verdict: COMPLIANT (one carried-forward caveat tied to the Step 17b blocking merge finding).
Assessed the committed PR tip (pr-4488) against PHILOSOPHY.md. Validated in a throwaway worktree — all changed-module lib + integration tests green (the local working tree is mid-merge; that state is harness-owned and untouched).
✅ Ruthless simplicity — PASS
gates.rsdecomposes the canary into one function per gate (run_smoke_gate,run_unit_test_gate,run_gym_baseline_gate,run_rpc_health_gate) plus two focused helpers. No speculative abstraction, no config-driven gate registry — the simplest structure that expresses the four concrete gates.- Diagnosability helpers (
extract_first_failure,sanitize_gate_detail) are proportionate to their stated goals (#4470);GATE_DETAIL_MAX_BYTES = 512is a justified bound, not gold-plating. - The
.ackmarker is deliberately a presence flag, not a data store — minimal payload, bounded disk + forgery blast radius. Occam's razor applied.
✅ Bricks & studs — PASS
quarantine_ack.rsis a textbook brick: one responsibility ("what is an acknowledgeable corrupt quarantine, and how do we durably ack it"), self-contained with tests, exposing clean studs —acknowledge,is_acknowledged,ack_marker_path,present_quarantine_artifacts,is_ack_marker_name.- Single source of truth honored: the module delegates to the canonical
crate::cmd_cleanup::is_corrupt_quarantine_nameso the cleanup sweep, the health probe, and the ack path "can never disagree about which artifacts are corrupt-quarantines" (its own doc comment). Re-export lives incmd_cleanup/mod.rs. This is exactly the contract-stability the brick model asks for.
✅ Zero-BS implementation — PASS
- No
TODO/FIXME/unimplemented!/todo!/placeholder in production code. - No production
unwrap/expect— every one is inside#[cfg(test)]. Errors flow throughSimardResult; subprocess failures are captured intoGateResult.detail(surfaced, not swallowed) including theErr(e)"failed to run" arm. - Non-destructive by construction:
acknowledgenever touches the quarantined artifact; refuses to follow a planted symlink/dir at the sidecar path (returnsErrrather than clobbering).
✅ No over-engineering — PASS
- Each new helper earns its place against a concrete requirement (#4469 durable ack, #4470 diagnosability + log-forgery defense). No premature generalization.
✅ Clean module boundaries — PASS
- Quarantine semantics single-sourced through one predicate; scan / probe / ack are thin callers. Boundaries are crisp and regeneratable.
⚠️ Carried-forward caveat (philosophy-relevant)
The blocking merge conflict flagged in the Step 17b code review is itself a single-source-of-truth risk: main and this PR both refactored remove_old_corrupt_dbs for #4469, and they sweep different directory sets (main: state_root + state/; this PR: state_root only). A naive textual conflict resolution would produce two divergent sweep behaviors — a direct violation of the "can never disagree" invariant this PR otherwise upholds. Resolve semantically (layer ack-awareness onto main's per-directory model) and re-validate, so the brick's contract stays intact post-merge.
Checklist
- Ruthless simplicity achieved
- Bricks & studs pattern followed
- Zero-BS implementation (no stubs, faked APIs, swallowed exceptions)
- No over-engineering
- Clean module boundaries
-
⚠️ Merge preserves single-source-of-truth — must be verified during conflict resolution (see caveat)
Bottom line: The implementation is philosophy-compliant and a good example of the brick model. The only outstanding concern is ensuring the merge resolution does not fracture the single-sourced quarantine semantics.
Resolve merge conflicts for the self-deploy quarantine-acknowledge work (#4488): - docs/reference/self-deploy-api.md: combine main's full NoQuarantineProbe reference section (window-scoped fresh/retained counts) with the PR's acknowledgement-aware counting note (#4469). - src/cmd_cleanup/disk.rs & src/self_deploy/health.rs: keep the semantic union of main's corrupt-DB sweep (top-level + live-store) and the PR's .ack sidecar awareness / protected-asset guard. Also unstage and gitignore the stray gym_history.db runtime artifact that was accidentally staged during conflict resolution. Validated: quarantine_ack (10), corrupt_db (14), self_health (6), auto_ack (4), aged_protected (5), quarantine_scan (9) — all passing on the merged tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…apper Step 9 refactor/simplify follow-up for the #4470/#4469 fix. Additive, non-breaking, no behavior change. - Remove the single-use private is_corrupt_quarantine_name wrapper in quarantine_ack.rs and call the canonical crate::cmd_cleanup::is_corrupt_quarantine_name directly, matching the pattern already used in self_deploy/health.rs. Removes redundant indirection; the delegation rationale is preserved as an inline comment. Build clean; clippy --all-targets --all-features clean; fmt clean; quarantine_ack lib tests (10) and self_deploy_convergence integration tests (4) all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…anners Step 9b performance pass for the #4470/#4469 fix. Both production directory scanners (scan_quarantine_candidates, tally_quarantine_files) forced a heap String via .to_string_lossy().to_string() for every directory entry, even though most entries are immediately skipped by the corrupt-quarantine / ack-marker predicates. Borrow the lossy Cow<str> instead so no String is allocated for the common skipped-entry case. Non-breaking, no behavior change (predicates take &str; Cow derefs). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two review-pass fixes: 1. Security (#4469): log auto-ack and manual-ack quarantine basenames via `?name` (Debug) instead of `%name` (Display) in self_deploy::health and operator_cli::self_health. A quarantine basename is an untrusted on-disk filename that may contain newlines/control chars; under the default non-JSON tracing subscriber, logging it raw would permit log-line forgery. 2. CI portability: the merge commit accidentally captured machine-specific absolute paths (/home/azureuser/...) in .github/hooks/amplihack-hooks.json, which break the hooks on CI and other checkouts. Restore the portable repo-relative paths from main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review (automated)
Scope reviewed: all 12 changed src//tests/ Rust files (+1564/−168), focused on the #4469 durable-quarantine-acknowledgement feature and the #4470 gate-diagnosability change.
Verification performed (evidence)
cargo test --lib quarantine→ 37 passed, 0 failedcargo test --lib gates::tests→ 13 passed, 0 failedcargo test --lib self_health→ 6 passed, 0 failedcargo clippy --lib→ 0 warnings, 0 errors (the PR's central-D warningsconcern is clean)
Checklist
- Code quality & standards — idiomatic, well-documented, single-sourced predicates (
is_corrupt_quarantine_name,select_protected_asset) so probe/sweep/ack can never disagree. - Test coverage adequate — new modules carry unit tests for happy path, idempotency, unsafe-name rejection, symlink defence, fresh-vs-retained tally, and age-gated auto-ack;
serial_test::serial(cognitive_memory)correctly added to env-touchingdecide.rstests. - No TODOs, stubs, or swallowed exceptions — errors are surfaced via structured
tracing::warn!andCleanupReport.errors; best-effort paths are deliberate and documented. - No unimplemented functions — none.
- Logic correctness —
.ack-sidecar exclusion is correctly ordered beforeis_corrupt_quarantine_name(the sidecar carries the.corrupt-infix), auto-ack is age-gated + idempotent, protected-asset selection is tie-broken to newest. - Edge case handling — TOCTOU closed with
O_EXCL/create_new;symlink_metadata(no follow) throughout; UTF-8-boundary-safe truncation; absent/unreadable dir ⇒ empty/0.
Security (verified strong)
acknowledge()refuses path-separator/../absolute/non-quarantine names, refuses to write through a planted symlink/dir sidecar, and closes the stat→open TOCTOU window withcreate_new(true).- Untrusted on-disk basenames are logged with
?name(Debug-escaped) to prevent log-line forgery — consistent acrosshealth.rsandself_health.rs. sanitize_gate_detailbounds subprocess output to 512 bytes and strips control chars before it reaches logs/JSON.
Non-blocking nits (optional)
sanitize_gate_detailANSI residue (src/self_relaunch/gates.rs): control bytes are collapsed, but the printable remainder of an ANSI escape (e.g.[31mafter the stripped\x1b) survives. Harmless (no forgery vector, the test only asserts\x1bis gone), but if you want fully clean output consider stripping\x1b[...msequences whole.- Pre-existing
eprintln!inremove_old_corrupt_dbs_in(src/cmd_cleanup/disk.rs) remains, which is inconsistent with the "structured tracing + OTel only" convention the rest of the feature follows. It predates this PR (out of scope), but is a candidate for a follow-up to convert totracing. - Operator
--acknowledge-quarantineacks all present quarantines, including fresh ones (unlike the age-gated auto-ack on the probe path). This appears intentional — it's a manual operator escape hatch and is documented as such — just flagging the asymmetry with the autonomous path for confirmation.
Verdict
High-quality, security-conscious, well-tested change. No blocking issues; targeted tests and clippy are green. The three items above are optional nits/follow-ups.
rysweet
left a comment
There was a problem hiding this comment.
Step 17c — Security Review (automated, MANDATORY)
Scope: security-focused review of the diff origin/main...HEAD for PR #4488 (12 Rust files, +1564/−168) — the #4469 durable quarantine-acknowledgement feature and the #4470 gate-diagnosability change. Reviewed independently (a dedicated security-review agent plus direct source tracing).
Verdict
No blocking issues. No Critical / High / Medium findings. The PR is unusually security-conscious; every new filesystem, logging, and parsing path was traced and its defense verified. Two LOW defense-in-depth consistency findings are noted below — both align with the PR's own stated threat model (untrusted on-disk quarantine basenames).
Security checklist
- All security requirements met — TOCTOU, symlink, path-traversal, and log-forgery defenses present and tested.
- No new vulnerabilities — no
unsafe, noCommand/shell exec, no deserialization of untrusted data introduced. - Sensitive data handling — no secrets logged; ack sidecar is a fixed 13-byte presence flag, never reads file contents.
- AuthN/AuthZ — autonomous auto-ack is strictly more restrictive than the operator path (single aged #2550 protected asset only, never deletes). No escalation/destructive divergence.
- Injection — no path traversal (
is_ackable_quarantine_basenamefunnels every path op); no SQL/command injection surface. Log/terminal injection: see LOW findings.
LOW-1 — Untrusted quarantine basename written unescaped to stderr / cleanup report
File: src/cmd_cleanup/disk.rs (the eprintln!(" Removing corrupt DB {} ...", cand.path.display(), ...) sweep line, and sibling report.errors.push(format!("failed to remove {}: {e}", cand.path.display()))). Severity: LOW · Confidence: 7/10 · Category: log/terminal forgery.
Path::display() does not neutralize control chars, so a quarantine artifact whose basename contains CR/LF or an ANSI ESC (it only needs the cognitive* prefix + .corrupt- infix to pass is_corrupt_quarantine_name) is emitted verbatim to the operator's terminal and stored raw in CleanupReport. This is notable because the PR's own threat model treats these basenames as untrusted and deliberately Debug-escapes them (?name) in health.rs / self_health.rs — the cleanup sweep is the one place that escaping wasn't applied.
Attacker scenario: a process with write access to <state_root>/state/ plants cognitive.corrupt-2026\r\n[FORGED] all clear (or ANSI escapes); an operator running the sweep sees the forged line / terminal-control rendered on their console. Why LOW: presupposes local write access to the state root (largely outside the primary threat boundary); the JSON report path escapes control chars via serde.
Fix (defense-in-depth, non-blocking): route the basename through the same escaping used elsewhere — Debug ({:?}) / tracing or a sanitize_gate_detail-style control-char strip — before display.
LOW-2 — Auto-ack marker path logged via Display while sibling field is Debug-escaped
File: src/self_deploy/health.rs, auto_ack_stuck_recovery_asset — marker = %marker.display(). Severity: LOW · Confidence: 6/10 · Category: log forgery.
The adjacent artifact = ?name field is correctly Debug-escaped to prevent log-line forgery, but the marker field is logged via Display (%…display()). The marker basename is {name}.ack, embedding the same untrusted quarantine name (which can pass validation with an embedded newline, since it is a single Component::Normal on Unix). Same log-forgery class as LOW-1, same local-write precondition. Fix: log the marker via ?marker (Debug) for consistency.
Verified defenses (no action needed)
- TOCTOU / symlink on ack write —
quarantine_ack::acknowledgestats withsymlink_metadata(no-follow), opens withcreate_new(true)(O_EXCL, never follows symlinks), and re-checks on theAlreadyExistsrace branch, refusing to write through a planted symlink/dir.acknowledge_refuses_to_overwrite_planted_symlink_marker(unix) confirms the victim is untouched. ✔ - Path traversal —
is_ackable_quarantine_basenamerejects empty,/,\, and requires exactly oneComponent::Normal== whole name (rejects..,., absolute/root/drive) before delegating tois_corrupt_quarantine_name; all three public fns funnel through it. Tests cover../escape,sub/…,/etc/passwd,... ✔ .ackreclamation & candidate scan —reclaim_ack_sidecar/scan_quarantine_candidatesusesymlink_metadata/entry.metadata()and unlink the link, never follow a symlink target. ✔- ANSI/terminal stripping —
sanitize_gate_detailcollapses everyis_control()run (NUL, CR/LF, tab, ESC 0x1B, C1/0x9B CSI) to one space and bounds to 512 bytes on a UTF-8 boundary;extract_first_failureroutes through it. Neutralizes ANSI (which requires ESC/CSI). ✔ - Log escaping on ack paths —
auto_ack_stuck_recovery_asset/acknowledge_all_present_quarantineslog basenames via?name(Debug). ✔ - No DoS — marker is a presence flag; no untrusted-length reads/allocations. ✔
- AuthZ asymmetry — autonomous path strictly narrower than operator path; non-destructive. ✔
Evidence: cargo test --lib (quarantine / gates / self_health) → 56 passed, 0 failed; cargo clippy --lib → 0 warnings, 0 errors.
Step 17d — Philosophy Guardian Review (automated)Scope: the 12 changed Compliance status
Non-blocking philosophy notes
Verdict: ✅ Philosophy-compliant. All five criteria pass. No blocking issues; the two notes are cosmetic/documentation-level. |
Address PR #4488 review findings: BLOCKING — probe/sweep directory-set asymmetry. The cleanup sweep reclaims both the top-level state root and the live-store `state/` subdir, but the self-health `no_quarantine` probe / auto-ack only scanned `state/`, so the probe and sweep could disagree on where quarantines live. Single-source the scan-dir set in `state_root::quarantine_scan_dirs` (deduped) and drive BOTH the sweep and the probe/auto-ack from it, so they can never diverge. Minor — orphaned `.ack` sidecars never reclaimed. Add `reclaim_orphaned_ack_sidecars`: a marker whose parent quarantine no longer exists is now reclaimed (regardless of remaining candidates), so stale markers cannot accumulate unbounded. LOW-1 — untrusted quarantine basename written unescaped to stderr / CleanupReport. Route operator-facing paths in the corrupt-DB sweep through the shared `sanitize_gate_detail` control-char strip (re-exported from `self_relaunch`) to prevent terminal/log forgery. LOW-2 — auto-ack `marker` field logged via Display while the sibling `artifact` is Debug-escaped. Log `marker` via `?marker` (Debug) for consistent control-char escaping. Tests: rework the top-level-scan test into a probe/sweep parity contract, add an orphan-`.ack`-reclaim test. All changed-module lib tests (78) + self_deploy_convergence (4) pass; clippy clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Step 17e — Blocking issues addressed (commit
|
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review (independent pass)
Scope: main...pr-4488 — 25 files, +2751/−189. Implements #4469 (durable .ack quarantine acknowledgement that breaks the stuck-quarantine self-deploy deadlock without deleting the #2550 recovery asset) and #4470 (canary failing-test diagnosability + hermetic test isolation via serial_guard/serial_test).
Verdict: APPROVE (no blocking findings). The two LOW log-forgery findings from the earlier Step 17b review are now resolved in the committed tip; nothing new blocks merge.
Verification performed (independent)
Built and tested the changed modules against the committed tip 228d4d24:
cargo test --lib --no-run→ compiles clean (59s).cargo clippy --lib→ 0 warnings, 0 errors.- Targeted lib tests — 95 passed, 0 failed:
quarantine_ack10 ·self_deploy::health::probe_logic_tests20 ·gates::13 ·self_health6 ·cmd_cleanup40 ·serial_guard6.
cargo test --test self_deploy_convergence→ 4 passed, 0 failed.- CI on the PR head: all required checks green (cargo-audit/deny/vet, coverage, pre-commit, install-real, e2e-dashboard, GitGuardian).
Checklist
- Code quality & standards — small, single-responsibility fns; every non-obvious decision documented; consistent error type (
SimardError::PersistentStoreIo). Refactor ofremove_old_corrupt_dbsintoscan_quarantine_candidates/select_protected_asset/remove_old_corrupt_dbs_insingle-sources the "protected asset" definition shared by the sweep and the probe auto-ack — they can no longer disagree. - Test coverage — new
quarantine_ackunit suite (path-traversal../,sub/…, absolute,..; TOCTOU/symlink refusal; idempotency), probe acknowledgement-aware tally tests, operator--acknowledge-quarantineend-to-end (live store never acked; idempotent), andself_deploy_convergenceintegration coverage.serial_guardmeta-test extended to catch theOodaConfig::default()env-read blind spot (#4433). - No TODOs / stubs / swallowed exceptions — none in production paths. Best-effort auto-ack/operator-ack failures are surfaced via structured
tracing::warn!(not silently dropped), which is the correct semantic here (converge-or-log, never crash the probe). - No unimplemented functions — none.
unwrap()/expect()appear only in#[cfg(test)]code. - Logic correctness —
.acksidecars are excluded beforeis_corrupt_quarantine_nameeverywhere it matters (the predicate matches*.ackvia the.corrupt-infix, and the ordering is correct inscan_quarantine_candidates,tally_quarantine_files, andpresent_quarantine_artifacts). Filename-keyed markers mean a freshcorrupt-<newts>still reddens the probe. Auto-ack fires only for the aged (#2550) protected asset — fresh corruption stays ineligible. - Edge cases — TOCTOU closed via
symlink_metadata(no-follow) +create_new/O_EXCL +AlreadyExistsre-check; planted symlink/dir at the sidecar path is refused, not written through; orphaned-sidecar reclamation handles a parent deleted out-of-band; UTF-8-boundary-safe truncation insanitize_gate_detail; absent/unreadable dirs ⇒ empty/no-op.
Confirmed fixes of the prior review's findings
- LOW-1 (unescaped basename in cleanup stderr/report) —
src/cmd_cleanup/disk.rsnow routes every operator-facing path throughsanitize_path_for_log→self_relaunch::sanitize_gate_detail(control-char strip, 4096-byte bound). ✔ - LOW-2 (auto-ack marker logged via Display) —
src/self_deploy/health.rsnow logsmarker = ?marker(Debug), consistent with theartifact = ?namesibling. ✔
Minor, non-blocking observations
eprintln!retained in the cleanup sweep (disk.rs"Removing corrupt DB …"). The #4469 "tracing/OTel only" rule is honored on the auto-ack/probe paths; this is the pre-existing operator-CLI progress convention (now with a sanitized path arg), not a regression. Consider migrating cleanup progress totracingin a future pass for uniformity.auto_ack_stuck_recovery_assetreturn value discarded (let _ = …per dir inrun_self_health_probe). The event is captured viatracing::warn!, but theSelfHealthReportdoesn't surface "auto-ack fired this run." Optional: thread the acked basename into the report for operator visibility.count_quarantine_filesis now#[cfg(test)]-only and re-derives the exclusion logic oftally_quarantine_files. Documented as intentional (kept for the ack unit tests); acceptable, minor duplication.
None of these change behavior or block merge.
Evidence: local cargo test/clippy runs above + green CI on the PR head. Scoped to #4469/#4470; scheduler problem #3 (VerifyAndMergePr prioritization) and cross-repo #4 (azlin) are correctly out of scope for this PR.
rysweet
left a comment
There was a problem hiding this comment.
Step 17c — Security Review (independent pass)
Scope: main...pr-4488 — 25 files. Traced the full flow: quarantine artifact discovery → .ack path derivation → creation/read gating → cleanup/reclamation. Reviewed live files: quarantine_ack.rs, health.rs, cmd_cleanup/disk.rs, operator_cli/self_health.rs, self_relaunch/gates.rs, state_root.rs, self_deploy/mod.rs.
Verdict: ✅ No high-confidence exploitable vulnerabilities. No blocking findings.
The .ack sidecar mechanism and log-sanitization changes are well-hardened, with explicit and correct defenses for exactly the in-scope attack classes.
Security controls verified sound
1. Path traversal / arbitrary file write — SAFE. ack_marker_path gates every read/write through is_ackable_quarantine_basename, which requires the name to be exactly one Component::Normal equal to the whole string — rejecting .., ., /, \, absolute paths, and root/drive prefixes. Combined with is_corrupt_quarantine_name, only cognitive*.corrupt-* siblings directly under state_root are addressable. Marker is always state_root.join("{name}.ack") — no attacker-influenced traversal. (Tests cover the traversal vectors.)
2. TOCTOU / symlink — SAFE. acknowledge does no-follow symlink_metadata first, refuses any non-regular file, then opens with create_new(true) (O_EXCL), which never follows a symlink and fails closed. The AlreadyExists branch re-stats no-follow and only accepts a regular file — stat→open window correctly closed. Both reclamation paths use symlink_metadata/DirEntry::metadata and remove a planted symlink as a link (never following to its target) — no arbitrary delete. (Planted-symlink defense is tested.)
3. Authorization / quarantine bypass — ACCEPTABLE (by design). Automatic auto_ack_stuck_recovery_asset fires only on the single #2550 protected recovery asset and only after it ages past 30 days. Fresh corruption is never eligible and still reddens the probe. The quarantine filename is minted by LadybugDB (cognitive.corrupt-<ts>), not attacker-controlled memory content — so untrusted memory data cannot craft a filename to self-acknowledge. Markers are filename-keyed, so acking an old artifact never silences a new corruption event.
4. Injection — SAFE. sanitize_gate_detail collapses every char::is_control() (\n,\r,\t,NUL,ANSI ESC) to a space and bounds to 512 bytes on a UTF-8 boundary; untrusted cargo test output is routed through it. sanitize_path_for_log routes untrusted operator-visible paths through the same sanitizer, closing log forgery via crafted basenames. Operator CLI logs basenames via Debug (?name). The cargo test subprocess uses Command::new + separate .arg() (no shell) — no argument injection.
5. Sensitive data — SAFE. .ack payload is a fixed 13-byte constant (b"acknowledged\n"). No secrets/tokens/PII written to sidecars, logs, or diagnostics.
6. DoS / panic — SAFE. Untrusted lengths bounded (512 / 4096 bytes); truncation is char-boundary-safe (no panic on multibyte UTF-8). No unbounded recursion/loops on crafted filenames.
7. Deserialization — SAFE. .ack contents are never parsed — presence of a regular file is the only signal. No untrusted deserialization introduced.
Non-blocking note (informational — not a finding)
sanitize_gate_detail/sanitize_path_for_log use char::is_control(), which covers C0/C1 controls (all ASCII newlines) but not Unicode line/paragraph separators U+2028/U+2029. Not a realistic forgery vector for the console/log sinks here (standard parsers/terminals don't treat U+2028 as a record separator), so not raised as a finding — optional defense-in-depth only if these strings ever reach a Unicode-newline-aware sink.
Security checklist
- ✅ Security requirements met (deadlock-break preserves fresh-corruption detection)
- ✅ No new vulnerabilities (path traversal, TOCTOU/symlink, injection all defended + tested)
- ✅ Sensitive data handling — no secrets/PII in sidecars or logs
- ✅ Authorization — auto-ack narrowly scoped (single protected asset, 30-day age gate); filenames not attacker-controlled
- ✅ Injection — log sanitizer +
?-debug + no-shell subprocess
Independently verified against committed code on branch pr-4488.
Step 17d — Philosophy Guardian Review (amplihack PHILOSOPHY.md)Scope: Verdict: ✅ COMPLIANT — no blocking philosophy violations. Compliance checklist
Minor, non-blocking observations
Conclusion: The PR embodies ruthless simplicity (net complexity reduction via single-sourcing), the brick/stud model, and zero-BS error handling. Added complexity is confined to security and data-integrity concerns the philosophy explicitly permits. Approved on philosophy grounds. |
|
Step 18b — Review Feedback Implementation: complete. All three Step 16/17 reviews (code, security, philosophy) returned APPROVE / COMPLIANT with zero blocking findings. No code changes are required for merge. The non-blocking suggestions (residual PR is mergeable as-is; CI green. |
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review (PR #4488)
Scope: 27 files, +2751/-189. Self-deploy red-canary convergence for the stuck-quarantine deadlock (#4469) + canary diagnosability (#4470) + OodaConfig::default() serial-test race hardening (#4433).
Verdict: APPROVE (no blocking findings). Verified locally — all touched test groups compile and pass:
self_deploy::quarantine_ack— 10 passedself_deploy::health::probe_logic_tests— 20 passedself_relaunch::gates::tests— 13 passedcmd_cleanup— 40 passedtests/self_deploy_convergence— 4 passed
All 17 required CI checks are green; mergeStateStatus: CLEAN, mergeable: MERGEABLE.
Review checklist
- Code quality and standards — idiomatic Rust, cohesive modules, docs explain the why (deadlock rationale, single-sourcing) not just the what.
- Test coverage adequate — TDD-style unit tests + an outside-in end-to-end convergence contract; edge cases (idempotency, fresh-after-ack, unsafe names, aged vs. fresh protected asset, orphaned sidecars) all covered.
- No TODOs, stubs, or swallowed exceptions — every error path returns
Error logs a structured WARN; notodo!/unimplemented!. All.unwrap()occurrences are in test code only. - No unimplemented functions.
- Logic correctness — see notes below; verified sound.
- Edge case handling — thorough.
Strengths (high confidence)
- Security posture is excellent.
acknowledge()usessymlink_metadata+OpenOptions::create_new(true)(O_EXCL) to close the TOCTOU window and refuses to write through a planted symlink/dir sidecar; a dedicated#[cfg(unix)]test proves the victim file is untouched. Path-traversal / separator / absolute /..names are rejected via a single-Component::Normalcheck. - Log-forgery hardening (#4470/#4469 LOW-1/LOW-2). Untrusted quarantine basenames and cargo-test output are neutralized:
sanitize_gate_detailstrips control chars, collapses whitespace, and bounds output UTF-8-boundary-safely; operator-facing paths route throughsanitize_path_for_log; tracing sites use Debug (?name) to escape control chars. - Single-sourcing prevents drift.
state_root::quarantine_scan_dirs(),is_corrupt_quarantine_name, andselect_protected_assetare shared by the cleanup sweep and the health probe, so the two can never disagree about where quarantines live or which is the #2550 protected asset — directly eliminating the root cause of the deadlock. - Non-destructive + idempotent + filename-keyed. Acknowledging retains the recovery asset; a stale ack never silences a new corruption event; orphaned
.ackmarkers are reclaimed even when the directory has zero live quarantines.
Non-blocking observations
- Health probe now has a write side-effect.
run_self_health_probecallsauto_ack_stuck_recovery_assetper scan dir, which writes a durable.acksidecar for an aged (>= CORRUPT_DB_MAX_AGE_DAYS) protected asset. A conceptually read-mostly probe now mutates the state root autonomously. It is well-guarded (aged and>= CORRUPT_DB_PROTECT_MIN_BYTES), documented, and best-effort (WARN + continue on failure), and the deadlock rationale justifies it. Flagging for awareness only — fresh corruption remains ineligible and still reddens the probe, so the fail-closed contract is preserved. eprintln!in the cleanup sweep (remove_old_corrupt_dbs_in) is retained (pre-existing) for the operator-facing removal report. This is a CLI cleanup path (not the probe path), so stderr is appropriate; the path argument is now sanitized. No action required.- Minor redundant I/O:
aged_protected_recovery_assetscans the dir, thenacknowledgere-stats the same sidecar, run once per scan dir on the probe path. Negligible (small dirs) — noted only for completeness.
Tie-break correctness
select_protected_asset uses max_by(size).then_with(modified), and Iterator::max_by returns the last maximal element, so size ties resolve to the newest artifact — matching the documented "ties → newest" contract. Correct.
Nothing here blocks merge. Clean, well-tested, security-conscious work.
🤖 Automated Step 17b review — Copilot CLI
Step 17c — Security Review (PR #4488)Scope: 27 files, +2751/-189 (self-deploy red-canary convergence: #4469 / #4470 / OodaConfig serial-test race). Read-only security analysis focused on the write/delete/relaunch primitives: Verdict: APPROVE (security) — no high-confidence, exploitable findings. Claimed defenses — verification
Additional checks (all clean)
File permissionsSidecars are created without an explicit mode → default Security checklist
Residual (non-reportable) noteAll primitives presuppose an attacker who already holds write access to the state root. Within that model the code correctly prevents traversal, symlink escape, and TOCTOU. The only reachable effect of a planted aged ≥1 MB |
Step 17d — Philosophy Guardian Review (PR #4488)Scope: self-deploy red-canary convergence — Compliance Checklist
Per-Principle Verdict
Overall Verdict: COMPLIANT-WITH-NOTES — ✅ approve, no blocking issuesGenuinely clean, not a rubber-stamp. The design attacks the actual deadlock (probe vs. sweep scanning different directory sets) by making both consume one source of truth; security hardening (O_EXCL closing the stat→open TOCTOU window, Blocking IssuesNone. Non-Blocking Notes
None affect correctness, safety, or data integrity. Posted by philosophy-guardian (Step 17d). |
Address optional philosophy/code-review notes from the Step 16 reviews. All items are non-blocking; the PR was already APPROVE with 0 blocking issues. S6: relocate the shared control-char log sanitizer out of `self_relaunch::gates` into a neutral `util::log_sanitize` module (`sanitize_to_single_line`). This removes the `cmd_cleanup::disk` -> `self_relaunch` cross-module coupling for a generic sanitizer; both the canary gate detail (#4470) and the cleanup path log (#4469, LOW-1) now depend on util instead. Sanitizer unit tests moved with it. S5: the test-only `count_quarantine_files` helper now delegates to the production `tally_quarantine_files` scan (summing fresh + retained) instead of duplicating the read_dir / acknowledgement filter logic, eliminating drift risk between the test helper and the live probe path. S7: document the intentional `let _ =` discard of `auto_ack_stuck_recovery_asset` in the no_quarantine probe loop (best-effort; errors logged internally). Also refresh docs/reference/self-deploy-quarantine-acknowledge.md to describe the actual production `tally_quarantine_files` scan rather than the now test-only helper. S4 (narrow `ack_marker_path` visibility) intentionally deferred: it is a documented public API and is consumed by the `self_deploy_convergence` integration test (a separate crate that requires `pub`), so narrowing to `pub(crate)` would break both. Verified: cargo test log_sanitize (3), self_relaunch::gates (10), self_deploy::health (20), quarantine_ack (10), cmd_cleanup (40), self_deploy_convergence e2e (4) — all pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hooks.json The Step 18b commit (d1163b4) inadvertently staged an unrelated change that rewrote the six committed hook `bash` entries from repo-relative paths (e.g. `.github/hooks/stop`) to an absolute, machine-specific path rooted at `/home/azureuser/src/Simard-deploy-4049/...`. Those absolute paths are a config divergence that would break the hooks on every other checkout and in CI. Restore the portable repo-relative paths. No source or behavior change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
rysweet
left a comment
There was a problem hiding this comment.
Step 17b — Comprehensive Code Review (PR #4488)
Verdict: APPROVE. High-quality, security-conscious implementation of the #4469 stuck-quarantine deadlock fix (plus #4470 canary diagnosability). Verified locally: crate compiles and all 50 targeted tests pass (quarantine 37, gates::tests 10, log_sanitize 3).
Review checklist
- Code quality and standards — clean, well-documented, single-sourced shared logic (
is_corrupt_quarantine_name,select_protected_asset,quarantine_scan_dirs) so the sweep and the health probe can never disagree. - Test coverage adequate — extensive: TOCTOU races, planted-symlink defense, idempotency, filename-keyed freshness, aged/fresh/trivial auto-ack eligibility, UTF-8 boundary bounding, first-failure extraction bounding.
- No TODOs, stubs, or swallowed exceptions — best-effort paths log structured
tracing::warnwith the error; no silent swallowing. All.unwrap()occurrences are test-only. - No unimplemented functions.
- Logic correctness — verified.
- Edge case handling — thorough (absent/unreadable dirs, orphaned sidecars, ties → newest, missing mtime fail-safe).
Strengths
- Security hardening is excellent.
acknowledge()closes the TOCTOU window withcreate_new/O_EXCL, refuses to write through a non-regular-file sidecar (planted symlink/dir), and rejects path-traversal / absolute / non-quarantine names. Log-forgery is neutralized both viautil::log_sanitize(cleanup path) and Debug-escaping (?name) on the probe path. - Single-sourcing. Extracting
quarantine_scan_dirs()andselect_protected_asset()eliminates the probe/sweep divergence that caused the original deadlock — the right structural fix, not a patch. - Non-destructive by construction. The #2550 recovery asset is retained; auto-ack only fires once the protected asset ages past the forensic window, and never for fresh corruption.
- Orphaned-sidecar reclamation (
reclaim_orphaned_ack_sidecars) closes the unbounded-marker-growth gap when a parent is deleted out-of-band.
Observations (non-blocking)
run_self_health_probenow has a write side effect. The probe path callsauto_ack_stuck_recovery_asset(), so a nominally read-only "health probe" now writes a durable.acksidecar. This is intentional and documented (it must fire for the unattended orchestrator post-deploy check), and it is tightly guarded (aged protected asset only). Flagging only because "probe mutates state" can surprise future readers — the inline comment already explains the rationale well.eprintln!remains inremove_old_corrupt_dbs_in(the "Removing corrupt DB …" line). This is the pre-existing pattern for the interactivecmd_cleanupCLI (not the daemon/probe path, which correctly usestracing), and the path is now routed throughsanitize_path_for_log, so it is not a regression. No change requested.is_ackable_quarantine_basenamecross-platform. The single-Normal-component check plus/and\\rejection is robust on the Unix daemon target; Windows drive-relative forms aren't a concern here.
Nothing blocking. Ready to merge once required CI checks are green.
rysweet
left a comment
There was a problem hiding this comment.
Step 17c — Security Review (PR #4488)
Verdict: PASS / APPROVE (security). No high- or medium-severity vulnerabilities found in the #4469 (durable quarantine .ack) + #4470 (canary diagnosability) changes. The implementation is notably security-conscious: it treats on-disk quarantine basenames as untrusted input and defends every sink (filesystem writes, destructive sweeps, operator logs).
Threat model
A corrupt-quarantine artifact only needs the cognitive* prefix + .corrupt- infix to be matched, so any principal with write access to the state root can plant a file/symlink with an attacker-chosen basename (including control chars). Reviewed against that adversary.
Security requirements — verified
| Check | Result | Evidence |
|---|---|---|
Path traversal (.., separators, absolute) |
✅ Defended | is_ackable_quarantine_basename rejects /, \, empty, .ack, and requires exactly one Component::Normal equal to the whole name; delegates to canonical is_corrupt_quarantine_name (quarantine_ack.rs:60-78). Tests cover ../, /etc/passwd, sub/…. |
| Symlink attack on sidecar write | ✅ Defended | acknowledge does symlink_metadata (no follow) then OpenOptions::create_new (O_EXCL, never follows a symlink, fails if path exists). Planted-symlink test asserts victim file is untouched (quarantine_ack.rs:123-158, 301-320). |
| TOCTOU on sidecar write | ✅ Defended | O_EXCL closes the stat→open window; AlreadyExists re-checks for a regular file before treating as idempotent success. |
| Log-line forgery / terminal-control injection | ✅ Defended | util::log_sanitize::sanitize_to_single_line collapses all char::is_control() runs (CR/LF/TAB/ANSI ESC/NUL) to a single space and bounds length on a UTF-8 boundary. Sweep routes every operator-visible path through sanitize_path_for_log; probe/CLI log the untrusted name via Debug (?name), which escapes control chars. |
| Symlink-following destructive delete | ✅ Safe | DirEntry::metadata() does not traverse symlinks, so a symlink named cognitive.corrupt-* → external dir yields is_dir=false and is removed with remove_file (unlinks the link, not the target). remove_dir_all only runs on a genuine directory entry (disk.rs:438). |
Orphaned .ack reclamation symlink safety |
✅ Defended | reclaim_orphaned_ack_sidecars / reclaim_ack_sidecar gate on symlink_metadata(...).file_type().is_file(); non-regular files are left untouched (disk.rs:249-273, 649-690). |
Env-var injection (SIMARD_STATE_ROOT) |
✅ Defended | sanitized_env_state_root rejects empty, NUL-bearing, and non-absolute values with a one-shot WARN (state_root.rs:123-144). |
| DoS via hostile long basename | ✅ Bounded | PATH_LOG_MAX_BYTES=4096, GATE_DETAIL_MAX_BYTES=512 cap all untrusted strings rendered to logs. |
| Sensitive-data handling | ✅ OK | Sidecar payload is a fixed 13-byte presence flag (acknowledged\n) — no secrets, no attacker-controlled content persisted. No credentials/tokens logged; only sanitized basenames + counts. |
| AuthN/AuthZ | ✅ N/A-appropriate | --acknowledge-quarantine is a local operator CLI action requiring filesystem access to the state root; non-destructive (never deletes the #2550 recovery asset), idempotent, best-effort per-artifact so one hostile entry can't block clearing the rest. |
| Command / shell injection | ✅ None | No process::Command/shell interpolation of untrusted data on these paths; untrusted basenames only reach fs APIs and sanitized log sinks. |
Non-blocking observations
- INFO — sanitizer scope.
sanitize_to_single_lineneutralizes C0/C1 controls (the real log-forgery vectors) but not Unicode format/bidi chars (e.g. U+202E RIGHT-TO-LEFT OVERRIDE), which can still visually reorder text in some log/terminal viewers. Very low risk for single-line operator output; consider also stripping the UnicodeCfcategory if spoofing-resistant operator logs become a requirement. - INFO — relative last-resort root.
home_default()falls back to a relative./.simardwhen neitherHOMEnordirs::home_dir()resolves.SIMARD_STATE_ROOTitself is required to be absolute, so this only affects the no-HOME edge case; benign and pre-existing, but worth noting since a relative state root places artifacts under CWD. - INFO — platform coverage. The planted-symlink refusal test is
#[cfg(unix)]. On Windows,create_new/O_EXCL still prevents overwrite and symlink creation is privileged, so the invariant holds; no action needed.
Verification
Crate compiles; targeted security-relevant tests pass locally (quarantine 37, gates::tests 10, log_sanitize 3). Path-traversal, planted-symlink, idempotency, and control-char-stripping cases are all covered by tests.
Conclusion: No injection, path-traversal, symlink, TOCTOU, or sensitive-data findings block merge. Approve from a security standpoint; the three INFO items are optional hardening, not required.
Step 17d — Philosophy Guardian Review (PR #4488)Verdict: PASS. The #4469 stuck-quarantine deadlock fix + #4470 canary diagnosability changes comply with the project philosophy (ruthless simplicity, bricks & studs, Zero-BS, no over-engineering, clean module boundaries). Compliance checklist
Non-blocking observations
Result: Philosophy-compliant. Nothing blocking merge. |
…t, and ack_marker_path visibility rationale Documentation-only comment additions addressing Step 19 review notes: - auto_ack_stuck_recovery_asset: document deliberate asymmetry vs manual acknowledge path (narrows to aged #2550 protected asset; never acks fresh corruption) - health probe: document the intentional durable .ack write side-effect - ack_marker_path: document why kept pub (self_deploy_convergence integration test in a separate crate asserts against it; narrowing breaks the build) No logic changes; affected suites green (quarantine_ack 10, health 20). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d ack_marker_path visibility (#4488 Step 18b) Apply non-blocking Step 16 review feedback (all reviews PASS; zero blocking issues). Doc/comment-only; no behavior change. - S3: document the no_quarantine probe's intentional .ack write side-effect on run_self_health_probe, and the deliberate manual-vs-auto acknowledge asymmetry on auto_ack_stuck_recovery_asset. - S1 (disagreement): keep ack_marker_path pub instead of narrowing to pub(crate) as suggested -- the self_deploy_convergence integration test (a separate crate) consumes it, so narrowing would break the build. Added a note at the definition to prevent the suggestion recurring. S2 (best-effort/error-visibility) was already covered by the existing comment at the auto_ack call site; no change needed. Verified: cargo build clean, cargo doc introduces no new warnings, 37 lib quarantine tests + 4 self_deploy_convergence integration tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📊 Coverage Summary
Coverage data from CI run. Test files matching |
Summary
Concise workflow-generated PR for Cargo.toml.
Issue
Closes #4470
Changed files
Diff stat
Behavior
Implemented behavior through these branch commits:
65c9b5c feat: ℹ NODE_OPTIONS=--max-old-space-size=32768 (saved preference). To chang
ec48d72 test(cmd_cleanup): fix corrupt-DB sweep test flakiness via path injection
3745f74 refactor(self-deploy): dedupe is_corrupt_quarantine_name + fix stale docs
dca6477 wip: checkpoint after implementation (steps 7-8)
Validation
ℹ NODE_OPTIONS=--max-old-space-size=32768 (saved preference). To change: /home/azureuser/.amplihack/config
I'll start by understanding the current state of the branch and what's changed.
● Get branch, status, diff stat, and recent commits (shell)
│ cd /home/azureuser/src/Simard-deploy-4049/worktrees/feat/issue-4470-nodeoptions-max-old-space-si
│ ze32768-saved-preferen && echo "=== BRANCH ===" && git branch --show-current && echo "=== STATUS
│ ===" && git status --short && echo "=== DIFF STAT vs main ===" && git diff --stat main...HEAD
│ 2>/dev/null | tail -40 && echo "=== RECENT COMMITS ===" && git --no-pager log --oneline -10
└ 32 lines…
There's an uncommitted change in
tests/adaptive_scaling.rs. Let me examine it and detect the toolchain.Risk
No high-risk subsystem pattern detected from changed paths.
Checklist
This PR was created as a draft for review before merging.
Step 16b: Outside-In Testing Results
Tested the PR branch from the external user/consumer boundary as a real operator would.
Detected toolchain: Rust CLI (
Cargo.tomlat root,simardbinary viabin.jsnpm wrapper). Per the qa-team repo-type detection,cargois the outside-in boundary — no gadugi framework required. Node/Playwright scripts (test:e2e) are unrelated to these self-deploy/self-health changes.Chosen strategy: Build the real
simardbinary and drive it as an operator (simard self-health …) against an isolatedSIMARD_STATE_ROOT, plus run the branch's own outside-in integration + changed-module unit tests. Theself-health --acknowledge-quarantineflow is the exact consumer boundary for the #4469 stuck-quarantine deadlock fix.Scenarios
cargo test --test self_deploy_convergence --test adaptive_scalingcargo test --lib self_deploy/self_relaunch::gates/cmd_cleanup/self_healthsimard self-health --help--acknowledge-quarantine(#4469)cognitive.corrupt-*under a tempSIMARD_STATE_ROOT, thensimard self-health --jsonthensimard self-health --acknowledge-quarantine --jsonScenario 4 — key output (the core fix)
Before acknowledge (deadlock state that could never clear):
After
--acknowledge-quarantine(converged):Retention check (non-destructive, #2550 recovery asset preserved):
Note: the overall
self-healthexit code stays non-zero in an empty hermetic state root because unrelated probes (version/entrypoint parity) have no live deployed binary to check against — expected for a throwaway root and independent of this fix. The targetedno_quarantineprobe deterministically transitions fromquarantined=true→false, proving a stuck quarantine can now clear so self-deploy converges.Fix count
0 fixes required. All outside-in scenarios passed on the branch as-is; no diagnose/fix/push iterations were needed.