Shell capture cleanup and generated-content policy guard#4298
Merged
Trecek merged 7 commits intoJul 20, 2026
Merged
Conversation
…ionStart sweep Removes the inline 'rm -f -- \"\$__as_f\"' deletion embedded in every Codex shell command's generated harness. Codex CLI 0.144.6's exec-policy engine forbids rm -f-style commands, causing universal command rejection. Cleanup now lives in a stdlib-only SessionStart TTL sweep via the new _capture_cleanup module. - Add src/autoskillit/hooks/_capture_cleanup.py with _is_safe_capture_file containment checks (symlink, hardlink, world-writable, traversal) and sweep_stale_captures deletion guarded by 1h age threshold - Widen shell_<uid> uid from 8 to 16 hex chars (collisions under fleet concurrency over the now 1h retention window) - Wire the sweep into session_start_hook.py alongside kitchen_state and pipeline_tracker sweeps (stdlib-only boundary preserved via inlined _CAPTURE_RE literal) - Add timeout_seconds=5 to shell_capture_hook HookDef matching the ceiling used by open_kitchen_guard / ask_user_question_guard / mcp_health_advisor - Add conformance tests: destructive-verb absence, small-output retention + sweep cleanup, symlink rejection, filename allowlist, containment parity vs core/path_containment.py, regex consistency between the two sweep sites - Invert the existing 'assert not artifacts' assertion to assert the Python-side retention invariant Fixes #4286 follow-up. Part A of the rectify plan only. Co-Authored-By: Claude <noreply@anthropic.com>
…generate snapshot hash
…mtime `sweep_stale_captures` previously derived its staleness threshold from `capture_dir_path.resolve(strict=True).stat().st_mtime` — the capture directory's own mtime, which only advances when an entry inside the directory is created, removed, or renamed. This meant a file created long before the directory's last entry-change could look "fresh" and be skipped, and files could go permanently unswept if the directory had no new entries for a while. Replace the directory-mtime proxy with `time.time()` so staleness is measured against real wall-clock time, matching the inline sweep block in `session_start_hook.py` (`_now.timestamp() - _cp.stat().st_mtime`). The strict-resolve existence/race check is retained; `capture_dir_resolved` is dropped since it is no longer referenced. Every existing test passes `max_age_seconds=0` immediately after creating the capture directory, so `dir_mtime ≈ wall_clock_now` at call time and the bug never surfaced. A new regression test in the next commit covers the case where the directory's mtime is equally stale to its contents.
…k vectors
`test_capture_cleanup_containment_parity` previously exercised only a
valid file, a symlink, and a traversal-shaped filename — leaving the
`st_nlink > 1` and `st_mode & 0o002` branches of `_is_safe_capture_file`
completely untested by the test whose stated purpose is to guard
exactly those branches.
Convert the test to `@pytest.mark.parametrize` over five module-level
case-builders covering all four attack vectors from
`core.path_containment.resolve_contained_path` (symlinks, traversals,
hardlinks, world-writable files), plus the valid baseline. Hardlink
case follows the existing `pytest.skip("hardlink not supported...")`
precedent for filesystems where hardlinks are unsupported.
Also add `test_sweep_stale_captures_uses_wall_clock_not_directory_mtime`
which backdates both a capture file and its enclosing directory by 200s
and asserts the sweep deletes it under `max_age_seconds=100`. This
reproduces the staleness-threshold defect fixed in the previous commit
(directory-mtime proxy instead of wall-clock time) and would have
failed against the prior implementation.
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Trecek
force-pushed
the
shell-capture-hook-injects-prohibited-rm-cleanup-and-blocks/4291
branch
from
July 20, 2026 05:58
2c18573 to
d248752
Compare
Trecek
deleted the
shell-capture-hook-injects-prohibited-rm-cleanup-and-blocks/4291
branch
July 20, 2026 06:13
This was referenced Jul 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/audit-implre-audited the Part A implementation of the shell-capture-hookrm -f→ Python-side-sweep migration against the original plan and found the implementation faithful on 47 of 49 tracked requirements. This PR fixes the two remaining defects — replacing the wrong time reference insweep_stale_capturesand parametrizing the parity test over all four attack vectors — and lands the broader shell-capture-cleanup architecture and generated-content policy guard that those fixes sit within.Individual Group Plans
Audit-implementation remediation
/audit-implre-audited the Part A implementation of the shell-capture-hookrm -f→ Python-side-sweep migration (commits12e85051e,29095a56b) against the original plan and found the implementation faithful on 47 of 49 tracked requirements. Two genuine defects remain and block merge:sweep_stale_captures(src/autoskillit/hooks/_capture_cleanup.py) computes staleness against the wrong time reference. It derives its "now" proxy fromcapture_dir_path.resolve(strict=True).stat().st_mtime— the capture directory's own mtime — instead of true wall-clock time (time.time()/datetime.now()). A directory's mtime only advances when an entry inside it is created, removed, or renamed; it is not a stand-in for "the current moment." This means a file created long before the directory's last entry-change can look "fresh" and be skipped, and files can go permanently unswept if the directory has had no new entries for a while — even though the siblingsession_start_hook.pyinline sweep block already implements the correct wall-clock semantics (_now = datetime.now(UTC);_c_age = _now.timestamp() - _cp.stat().st_mtime). Every existing test masks this because each one callssweep_stale_captures(..., max_age_seconds=0)immediately after creating the capture directory, sodir_mtime ≈ wall_clock_nowat call time and the bug never surfaces.test_capture_cleanup_containment_parity(tests/hooks/test_shell_capture_conformance.py) omits two of the four required attack vectors and is not parametrized. The original plan (Step 2g) requires this test to verify_is_safe_capture_filerejects the same attack vectors as the canonicalresolve_contained_path(core/path_containment.py): symlinks, traversals, hardlinks, and world-writable files, via parametrized cases, so future drift between the two independently-maintained implementations is caught. The current test only exercises a valid file, a symlink, and a traversal-shaped filename — it never constructs a hardlink or a world-writable file, so thest_nlink > 1andst_mode & 0o002branches of_is_safe_capture_fileare completely untested by the test whose stated purpose is to guard exactly those branches.This plan fixes both defects with the minimum change needed to make the implementation match its own stated design intent. It does not touch the generated-content policy guard, live conformance probe strengthening, or
codex_statusverification architecture — those remain reserved for Part B (a separate task, out of scope here) per the original plan's Part A/B split.Shell capture cleanup architecture
shell_capture_hook.py:113injectsrm -f -- "$__as_f"into every Codex shell command's generated harness. Codex CLI 0.144.6's exec-policy engine (a compiled-in rule introduced atrust-v0.144.5) forbidsrm -f-style commands, causing universal command rejection. The inline shell deletion is architecturally anomalous — it is the only shell-embedded deletion in the entire package — and the "spill" branch (>12KB output) never cleaned up at all.This part removes the shell-embedded deletion, replaces it with a Python-side stdlib-only cleanup module in the hook subprocess, adds a TTL sweep for orphaned captures, and updates the conformance tests and documentation to match the new behavior.
Part B will cover the generated-content policy guard, live conformance probe strengthening, and
codex_statusverification architecture — implement as a separate task.Generated-content policy guard and conformance verification
This part addresses the recurring architectural gap: hook-generated artifacts (shell scripts, TOML config) are validated against models/snapshots, never against the installed Codex binary's actual enforcement layers. This is the third incarnation of this failure class (write_guard divergences, output_budget_guard false positives, shell_capture policy collision).
The solution introduces:
Part A covered the
rm -fremoval, Python-side cleanup, and sweep architecture.Implementation Plan
Plan files:
/home/talon/projects/autoskillit-runs/remediation-20260718-192507-037270/.autoskillit/temp/rectify/rectify_shell_capture_hook_rm_cleanup_and_generated_content_guard_2026-07-18_192507_part_a.md/home/talon/projects/autoskillit-runs/remediation-20260718-192507-037270/.autoskillit/temp/make-plan/shell_capture_hook_rm_cleanup_remediation_plan_2026-07-20_041550.md/home/talon/projects/autoskillit-runs/remediation-20260718-192507-037270/.autoskillit/temp/rectify/rectify_shell_capture_hook_rm_cleanup_and_generated_content_guard_2026-07-18_192507_part_b.mdCloses #4291
🤖 Generated with Claude Code via AutoSkillit
Token Usage Summary
* Step used a non-Anthropic provider; caching behavior may differ.
Token Efficiency
Model Usage Breakdown