Repository navigation
Description
What
A checkout's content filters run programs that the Git effect's transaction cannot take back.
filter.<driver>.clean / .smudge / .process name programs. They are reached from .gitattributes in the tree and defined in the checkout's own .git/config — both of which live inside the Workspace this run retains, so a document can write them and a replay restores them.
Reproduced against real Git, with the exact configuration #483 fixes:
printf '* filter=evil\n' > .gitattributes
git config filter.evil.clean "$PWD/cleaner" # writes a sentinel, then cats
git -c core.hooksPath=/dev/null -c core.fsmonitor=false \
-c commit.gpgSign=false -c tag.gpgSign=false add f.txt
# → the sentinel is written: the clean filter ranWhere it reaches
<Git.Add>(📥 Stage exactly the paths a document names #477) —git addrunscleanon the content it stages.<Git.Switch>(🔀 Switch a retained checkout's branch inside a workflow run #474) — the checkout it performs runssmudge.
Not <Git.Commit> specifically: #483 closed hooks, signing and the file-system monitor for every command this provider runs, and this survives that.
Severity
This is not remote code execution. git clone does not import arbitrary configuration keys from a remote, so a hostile remote alone cannot arm it — the .git/config naming the driver has to be written by the run itself, and a document that can do that can already run programs by other means.
What is actually at stake is the same property #483 describes for hooks: transaction integrity and replay determinism. A program that runs here escapes the effect that would otherwise roll it back, and it is a side effect a replay cannot reproduce.
Why it is not a one-line fix
There is no blanket disable. Filter drivers are named individually, so there is no -c equivalent to core.hooksPath=/dev/null. Closing it would mean something like reading filter.* out of the materialized configuration and blanking each clean, smudge and process before every mutating command — a new per-command step inside performGitOperation()'s envelope.
Decision
Closed as wontfix. A checkout configuring its own content filters is not a case expected during workflow execution, and the cost of the envelope change is not worth carrying for it. Reopen if a workflow turns up that legitimately retains filter configuration, or if the threat model changes such that a remote can arm one.
Recorded so the analysis is not lost with PR #483's review thread.
Activity
taras commentedon Aug 18, 2026
Not a case expected during workflow execution: arming it requires the run to write
filter.<driver>.*into its own retained.git/config, and a document that can do that can already run programs by other means. A hostile remote cannot arm it on its own, because clone imports no arbitrary configuration keys.Left as a decision record. Reopen if a workflow legitimately retains filter configuration, or if the threat model changes.