Skip to content

disk-hygiene: _engine_gate_relevant samefile-scans every token of every command, not just separator-carrying words as documented #3527

Description

@kyle-sexton

Summary

_engine_gate_relevant's marker-free fallback calls os.path.samefile on every whitespace token of every Bash/PowerShell command in every session. Its own docstring says it scans separator-carrying words; the implementation applies no such filter. This is both a doc/code mismatch and the identified source of the unbounded stall that #1423 investigated and that PR #3523 works around rather than fixes.

Evidence

plugins/disk-hygiene/skills/clean/scripts/destructive_guard.py, marker-free branch of _engine_gate_relevant:

candidates = list(marker_candidates)
words = _literal_shell_words(command, allow_backslash=allow_backslash)
candidates += (
    [token.strip("'\"") for token in command.split()]
    if words is None
    else list(words)
)
return any(_same_file_as_bundled(candidate) for candidate in candidates)

Every candidate reaches _same_file_as_bundled, which calls os.path.samefile(word, bundled) and, for relative words, a second samefile against the engine's own directory. So an ordinary git log --oneline --graph --decorate origin/main performs a filesystem identity check on each of its words.

The function's docstring describes something narrower:

A path-like word (containing a separator) that is the SAME FILE as the bundled engine — a symlink or hard link under any name — gates regardless of its filename.

and the branch's own inline comment says:

scan their whitespace tokens for separator-carrying words and identity-check those

Neither the whitespace-token path nor the _literal_shell_words path filters on a separator.

Why it matters

The module docstring already names this as the strongest candidate for the one observed 17-second hang:

the strongest identified candidate for the 17s itself is _engine_gate_relevant's marker-free fallback, which calls os.path.samefile on every separator-containing word of every Bash/PowerShell command in every session

Note that sentence says "every separator-containing word", which is what the code was believed to do. It scans every word.

The latency is unbounded, not merely large: samefile on a dead drive letter, a disconnected UNC path, or a stale network mount blocks for as long as the OS takes to fail. No deadline value fixes that — it is why PR #3523 changed the watchdog's expiry action instead of tuning its timeout. The stall itself is still here.

Two distinct costs:

  1. Latency, paid by every shell tool call in every session, proportional to token count.
  2. Reach. A guard that only governs disk-hygiene engine invocations performs filesystem calls against arbitrary user-supplied path tokens from entirely unrelated commands.

Suggested fix

Filter the marker-free candidate set to separator-carrying words, which is what both the docstring and the inline comment already describe.

This is a semantics change to the security core and should not ride along in a performance PR. It needs its own review of the residual set, because it narrows what the gate sees. The relevant question for a reviewer: the function's contract already lists

a PATH-installed alias with no separator, an alias inside a command the literal parser rejects when the marker is absent, and a copied engine

as accepted residuals of the copy-evasion class. A separator filter appears to add nothing beyond the first of those, which is already accepted — but that needs confirming, not assuming, and the marker-carrying branches must stay untouched.

Verification expected of a fix

  • A differential over a command corpus proving allow/ask/deny parity on every non-alias shape (PR perf(disk-hygiene): cut the destructive guard's per-call spawns 4 to 1, and stop its watchdog blocking commands it never judged #3523 added a harness of this shape that can be reused).
  • Explicit cases for a hard-linked engine under a non-marker name, invoked with and without a separator, since that is the class the filter could narrow. Note os.link cannot cross volumes on Windows; a cross-volume failure silently degrades such a test into a copy test, which exercises a different, already-accepted residual and reports no gap.
  • A measurement of samefile calls per invocation, before and after, on a representative command.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: highSignificant impact, or blocks an imminent release; staff this cycle.work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions