Repository navigation
fix: never run a repository's core.fsmonitor on git index reads - #409
Merged
Merged
Conversation
git runs the command a repository's config names in core.fsmonitor whenever it reads the index - ls-files, status and diff included. The hooks and the HUD run inside arbitrary repositories, so each unguarded index read executed code the repository chose. Every remaining index read now passes -c core.fsmonitor=false (D-NO-FSMONITOR), matching resolve-settings and verify-evidence: - json-helper listGitTrackedFiles (assign-anchor collision scan) - pre-compact-memory git status / git diff - background-memory-update git status / git diff - the HUD's gitExec wrapper (status and diff on every prompt) Regression tests arm a real repository with a recording fsmonitor hook and assert it never runs while the reported git state stays correct, with a known-bad unguarded probe proving the hook is live.
A static guard over src/assets/scripts and src/**: each git call spelled with an index-reading subcommand (shell, command string, argv literal or git-named wrapper) must carry core.fsmonitor=false before the subcommand, or go through a wrapper that prepends it. Seeded probes pin every spelling red and prose, guarded calls and non-index subcommands green. Against the pre-fix tree it reports exactly the seven sites the previous commit guarded.
The HUD passed -c core.fsmonitor=false on every call, which also turned off git's built-in fsmonitor daemon. That daemon is git's own code, not a command from the repository, and it is what keeps status fast in huge repositories, where the HUD's 1s timeout would otherwise expire and draw a clean tree. Each refresh now reads core.fsmonitor once with git config --type=bool --get (no index read, never cached). Only the literal answer true lets status and diff run without the override. A hook path (refused by --type=bool), any other value, an unset key and a failed read keep it (fail closed). Every other HUD call keeps the override unconditionally. The static guard honours the conditional wrapper only in src/hud/git.ts, only while that file defines fsmonitorOverride itself, and runs the classifier to require it to fail closed for every answer but true.
shellExec trimmed every call's whole stdout, which also stripped the leading blank of the first `git status --porcelain` line. An unstaged edit prints ` M path`; trimmed, `M` moved into the index column, so a tree whose only change was an unstaged edit to a tracked file read as staged and clean. shellExec now takes a trim mode: whole-output trim stays the default for single values (refs, counts, the config answer), and the status call trims trailing whitespace only.
dean0x
added a commit
that referenced
this pull request
Sep 30, 2026
The fsmonitor guard and HUD status-trim fix landed in #409 without an [Unreleased] entry; add them before the 3.0.0 release.
dean0x
added a commit
that referenced
this pull request
Sep 30, 2026
Add a changelog-coverage pre-release check (#409 shipped without an [Unreleased] entry) and replace the stale compliance-gated post-release note: evidence extras now follow EVIDENCE_POLICY=required and are appended to the CI-created release. Verify npm via dist-tags, which does not lag like `npm view … version` did.
7 tasks
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
Git runs the command named by a repository's
core.fsmonitorconfig on every index read —ls-files,status,diff— even for "read-only" commands. Devflow's hooks and HUD run inside arbitrary user repositories, so a crafted repo config (e.g. a repo shared as a tarball/zip including.git, or on a shared filesystem) could execute code the moment devflow reads the index. This PR guards every shipped index read with-c core.fsmonitor=false.Changes
-c core.fsmonitor=false:src/assets/scripts/hooks/json-helper.cjs(listGitTrackedFiles/ assign-anchor collision check)pre-compact-memory(status, diff)background-memory-update(status, diff)src/hud/git.ts(thegitExecwrapper)D-NO-FSMONITOR, also retroactively applied to the two already-guarded sites (resolve-settings.cjs,verify-evidence.cjs)git config --type=bool --get core.fsmonitoronce per refresh (not an index read, never runs the hook). Only an exacttrue(git's built-in daemon — needed to keep status/diff fast in huge repos under the HUD's 1s timeout) skips the override on status/diff. A hook path,false, unset, or a read failure keeps the override (fail closed)..trim()ongit status --porcelainstripped the first line's leading space, so a lone unstaged edit to a tracked file read as staged and the tree as clean. Status now trims trailing whitespace only.tests/guards/no-fsmonitor-index-read.test.ts) that fails on any future unguarded shipped git index read.Breaking Changes
None.
Testing
tests/decisions/ledger-ops.test.ts,tests/memory-hooks-fsmonitor.test.ts(new),tests/integration/hud-git.test.tstests/hud-git-fsmonitor.test.ts(new)tests/guards/no-fsmonitor-index-read.test.ts(new)tsc --noEmit,typecheck:scripts,tests/guards(272), HUD unit (156), ledger-ops + memory-hooks-fsmonitor (114), hud-git integration (30)npm testsuite (CI covers it); Snyk (local MCP broken)Related Issues
Closes #408