fix(disk-hygiene): reason about volume-root rejection instead of blanket-denying non-OS drives#1063
Conversation
…ket-denying non-OS drives Single-commit re-application of the fully reviewed and CI-green tree from PR #1026, rebuilt on current main so every commit in the PR branch carries a verified signature as the repository requires. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFq1q99cNeZjKhDzHv2BQw
|
Claude finished @kyle-sexton's task in 4m 1s —— View job PR Review:
|
| Test | What it pins |
|---|---|
test_os_drive_markers_exclude_per_volume_metadata |
$Recycle.Bin/SVI absent from OS-drive markers |
test_os_managed_target_flags_drive_holding_os_install |
Marker existence → OS-managed |
test_os_managed_target_ignores_per_volume_metadata_only |
No OS-install marker → not OS-managed (the Dev Drive case) |
test_os_managed_target_flags_path_within_a_system_root |
Target inside a system root → OS-managed |
test_hard_protection_reasons_about_volume_root_purpose |
Branch is reasoned, not structural |
test_hard_protection_exempts_admitted_volume_root_from_mount_reason |
No spurious mount reason on children |
test_non_os_volume_root_without_bound_requires_large_confirmation |
End-to-end: exit 5, non-os-volume-root reason |
test_non_os_volume_root_with_confirmed_flag_scans |
End-to-end: --confirmed-large-scan proceeds |
test_large_scan_reasons_flags_non_os_volume_root |
Both OS and non-OS volume-root cases |
test_scan_denies_os_managed_root |
OS drive still denied |
test_scan_rejects_non_root_mount_point |
Non-root mount still rejected |
test_preview_denies_os_managed_root_snapshot |
Preview re-checks OS-managed status |
test_preview_allows_non_os_volume_root_snapshot |
Preview passes non-OS volume root through to candidate eval |
The _non_os_volume_root_patches() helper correctly mocks is_volume_root, is_os_managed_target, and mount_state to simulate a Windows Dev Drive on Linux CI without needing actual Windows APIs.
Low-severity observations (no action required)
1. Docstring ambiguity in is_os_managed_target() (line 309)
"The containing direction deliberately uses the narrow marker set"
"Containing direction" is slightly ambiguous — a reader might think it refers to the first check (is target contained in a root?) rather than the second (does target contain a marker?). The code is correct and the tests verify it, but "marker-contains direction" or "target-contains-marker direction" would remove the ambiguity.
2. Redundant GetLogicalDrives calls for non-OS volume roots (acknowledged as advisory #2 in the PR)
For a non-OS volume root, windows_drive_roots() is called 4× per scan invocation: twice inside the is_os_managed_target() call in main() (once from system_roots(), once from os_drive_markers()), and twice more from the is_os_managed_target() inside large_scan_reasons(). Negligible for a non-hot-path scan entry point.
3. hard_protection() volume-root branch unreachability (acknowledged as advisory #1 in the PR)
The is_volume_root(path) and is_os_managed_target(path) branch in hard_protection() (line 338) is dead code on the live call path (candidates are validated non-root descendants). Keeping it reasoned rather than blanket is the right choice for defense-in-depth, and locking it with a direct unit test (test_hard_protection_reasons_about_volume_root_purpose) is the right mitigation.
Documentation
SKILL.md: The "Arguments and boundaries" section now correctly names D:\ as a valid target subject to the large-target gate. The distinction between OS-managed root (denied outright) vs. non-OS volume root (valid but confirmation-required) is clear. Lines 27–30
safety-model.md: Non-overridable checks section accurately reflects the new distinction. Lines 20–22
evals.json: Eval #11 covers the correct expected behavior — the non-OS volume root is valid, hits the large-target gate, and an OS-managed root is still denied. Lines 127–135
CHANGELOG.md: Accurate and complete.
plugin.json: Version bump to 0.6.4 is correct.
Verdict
Approved. The classification core is sound, every branch has been hand-traced, deletion safety is unchanged, and the PR degrades safely under failure modes (stray OS-marker folder on a non-boot drive → conservative over-restrictive deny, not an unsafe allow). No blocking issues.
|
Claude finished @kyle-sexton's task in 3m 55s —— View job Security Review
Reviewed the seven changed files. No critical vulnerabilities found. Two findings worth noting: IMPORTANT — TOCTOU race window in
|
Note
Supersedes #1026 — same fully reviewed, CI-green content rebuilt as a single commit on current
main (version reconciled to 0.6.4 over #1014's 0.6.3). See #1026 for the complete review history;
all its review threads were resolved there.
Summary
disk-hygiene'scleanskill rejected any filesystem/volume root as an audit target purelystructurally, with no reasoning about the volume's purpose. This wrongly blocked a legitimate non-OS
volume such as a Windows Dev Drive (
D:\, ReFS, no OS content) with no path to override,interrogate, or explain. Root classification is now reasoned: a genuine OS-managed root stays
denied; a non-OS volume root becomes a valid target that composes with the large-target scan gate.
Fixes #984
Root cause (corrected from the issue's premise)
The issue cited
hard_protection()(thepath.parent == pathbranch) as the blanket rejection.Empirically, that branch is dead code for drive roots in the live call graph — candidates are
validated non-root descendants of the target, so it never fires on a drive root. The real
user-facing block on Windows was the mount-point check in
main()scan: every Windows driveletter is
os.path.ismount() == True, soD:\was rejected as a "mount point" before theroot check the issue points at (verified live:
os.path.ismount('D:/') == True; scanningD:\returned
{"error": "mount points are not valid audit targets"}). Fixing only the cited line wouldhave left the bug unfixed.
preview()applied the same checks in a different order, a latentinconsistency.
Fix
is_os_managed_target): a root is denied when it is withinan OS-managed root (full
system_roots()set) or holds an existing OS-install marker(
os_drive_markers). The OS-install markers deliberately exclude the per-volume metadata everyWindows volume carries (
System Volume Information,$Recycle.Bin) — a Dev Drive has those too,so counting them would misclassify every drive root as OS-managed.
WINDOWS_VOLUME_SYSTEM_NAMESissplit into
WINDOWS_OS_DRIVE_MARKERS∪WINDOWS_PER_VOLUME_METADATA; the union is unchanged, soper-entry protection of those folders on every drive is preserved.
mounted and not is_volume_root(target)): avolume-root target (inherently a mount point) falls through to the reasoned root logic; nested and
bind mounts stay hard-blocked — the real cross-boundary danger.
unverified → OS-managed → non-root-mounttarget-check order,removing the latent ordering inconsistency.
large_scan_reasonsnow names a non-OS volume root a known-large root (reasonnon-os-volume-root), so an unbounded whole-volume walk returnslarge-target-confirmation-required(exit 5) unless bounded with
--max-depthor confirmed with--confirmed-large-scan. Nodestructive_guardchange is needed — feat(disk-hygiene): gate unbounded scans of known-large targets at the engine #1010 already permits that flag.hard_protection's (dead-for-drive-roots) branch is made reasoned rather than blanket, forfaithfulness, and locked with a direct unit test.
Deletion safety is unchanged: per-entry OS-managed / mount / VCS / identity protections, the preview,
and per-tier approval all still gate any removal. The design degrades safely — if OS-drive
markers were somehow absent on a real OS drive, the result is confirmation-required plus full
downstream gating, never a silent dangerous delete. A stray non-OS
D:\Windowsfolder classifies thevolume as OS-managed (conservative false-positive deny), which is acceptable.
Test evidence
test_hygiene.py: 101 tests pass (added classification,os_drive_markersmetadata-exclusion,hard_protectionreasoning, non-root-mount rejection, OS-managed deny in scan+preview, andnon-OS-volume-root large-target-gate tests; existing
system_rootsprotection test stays green).ruff checkclean;python -m py_compileclean.check-changelog-parity --check-bump,check-changed-skills(skill-qualitystatic contract — 0 errors),
validate-plugins(manifests + catalog),check-orphaned-fixtures.C:\→ denied (OS-managed roots are not valid audit targets);D:\(Dev Drive) with no bound →large-target-confirmation-required(reasonnon-os-volume-root);D:\ --max-depth 1 --confirmed-large-scan→scan-complete.Independent review
Reviewed by a fresh-context reviewer (rationale withheld, adversarial). Verdict: classification core
sound — every branch hand-traced, the pathlib root-identity edge verified empirically, no path where
an OS drive slips through as non-OS or a Dev Drive is wrongly denied, and no deletion-safety
regression. No code changes required. Three low-confidence advisories, noted for transparency (no
action taken):
hard_protection's volume-root branch is unreachable on the live call path (candidates arevalidated non-root descendants) — kept as defense-in-depth and locked by a direct unit test.
system_roots()andos_drive_markers()each callwindows_drive_roots(), so a scan makes acouple of redundant
GetLogicalDrivessyscalls per invocation — negligible, not hot-path.Recovery(or other OS-install-marker-named) folder on a non-boot drivewould classify that volume as OS-managed. This fails safe (over-restrictive deny, never an unsafe
allow) and is the conservative side to err on.
Fresh-docs note
Per the repo's fresh-docs mandate: this change is Python engine logic that reuses existing status
vocabulary and adds no new plugin component type, manifest field, hook, or skill surface — no official
Claude Code plugin-schema page is load-bearing here, so none is cited. The only external behavior
reused (
--confirmed-large-scan,large-target-confirmation-required) is this plugin's own #1010contract, referenced directly.
Related
large_scan_reasons/--confirmed-large-scanmechanism rather than adding a parallel gate.🤖 Generated with Claude Code
https://claude.ai/code/session_01SFq1q99cNeZjKhDzHv2BQw