Skip to content

Report a dirty worktree only when git status does - #161

Open
Ibrahimrahhal wants to merge 1 commit into
mainfrom
cursor/dirty-worktree-matches-git-status-ff66
Open

Report a dirty worktree only when git status does#161
Ibrahimrahhal wants to merge 1 commit into
mainfrom
cursor/dirty-worktree-matches-git-status-ff66

Conversation

@Ibrahimrahhal

Copy link
Copy Markdown
Member

Summary

A client saw corgea scan report a dirty worktree on a checkout their own git status called clean. Dirtiness is now exactly what git status reports, asked of git itself.

Two things made the old answer diverge from the user's:

  • The upload flag and the --skip-if-commit-scanned-recently reuse gate also counted index hide-bits — assume-unchanged and skip-worktree — as dirtiness. A sparse checkout sets skip-worktree on every file it leaves out, so an entirely clean repository read as dirty.
  • The status itself came from libgit2, which disagrees with git wherever it cannot reproduce git's work: it runs no clean filters (git-lfs and friends), cannot read a sparse index, and knows nothing of a status.showUntrackedFiles preference. Each disagreement surfaced as an uncommitted change git status does not show.

git --no-optional-locks status --porcelain now answers, run at the worktree root with git's repository-pinning environment variables scrubbed (so a scan invoked from a hook still reads the tree it was pointed at), and --no-optional-locks so a read never writes the user's index. libgit2 stands in only when the git binary cannot answer, and a status nobody can produce still fails closed to dirty.

That collapses RepoInfo's two flags into one, so the scan upload's dirty, the reuse gate, and the "Working tree has uncommitted changes" notice all describe the same state — the one a user can check for themselves. The upload flag still marks a --target/--exclude archive and SHA drift during packaging as not-an-exact-commit-snapshot; only worktree sampling changed.

Behaviour change

--skip-if-commit-scanned-recently now reuses a scan on a tree whose only differences are invisible to git status (assume-unchanged, skip-worktree, filtered content). --ignore-dirty-worktree is no longer needed for those cases; it keeps working for a genuinely dirty tree and for a prior scan that recorded worktree_dirty.

Test plan

  • ./harness check — clippy (fix + strict), format, 749 tests, suppression report: all green.
  • New unit tests: assume-unchanged, skip-worktree, and a clean-filter checkout (git-lfs shape, asserting libgit2 reads it as modified while the CLI does not) all report clean; the libgit2 fallback is exercised directly.
  • New end-to-end test scan_clean_by_git_status_sends_dirty_false_without_worktree_notice: a filtered checkout uploads dirty=false and prints no notice. Against the pre-fix source it fails with missing multipart field dirty="false".
  • Rewrote a_file_hidden_from_git_status_*: an assume-unchanged edit now reuses the commit's scan instead of scanning, and the --ignore-dirty-worktree variant of it is gone with the behaviour it covered.
  • Real binaries, pre-fix and post-fix, run against a stub API in a repository with a clean filter, an assume-unchanged file and stale timestamps — full output in the log below.

./harness ci stops at Dep audit (cargo-audit not installed) in this environment; no dependency changed in this PR.

dirty_worktree_before_after.log

dirty_worktree_tests.log

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

A client's scan reported a dirty worktree on a tree their own git status
called clean. Two sources of that: the CLI also treated assume-unchanged
and skip-worktree index bits as dirtiness (a sparse checkout sets the
latter on every file it leaves out), and libgit2's own status disagrees
with git wherever it cannot reproduce git's work - it runs no clean
filters (git-lfs and friends), cannot read a sparse index, and ignores a
status.showUntrackedFiles preference.

Dirtiness is now what git status reports, asked of git itself, with
libgit2 standing in only when the git binary cannot answer. That
collapses the upload flag and the user notice into one signal, so the
scan, the --skip-if-commit-scanned-recently reuse gate, and the notice
all describe the state a user can check for themselves.

Co-authored-by: ibrahim <ibrahim@corgea.com>
@Ibrahimrahhal
Ibrahimrahhal marked this pull request as ready for review August 23, 2026 11:23

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actionable findings; I found no reason this change should block merging.

I verified the merge-base diff and all dirty-state call sites: upload reconciliation still fails closed on missing samples or SHA drift, --target/--exclude still force the upload dirty bit without producing a worktree notice, and skip-scan reuse now consumes the same Git-status signal as upload and the notice. The tests cover tracked, staged, untracked, ignored, assume-unchanged, skip-worktree, clean-filter, submodule, upload, and reuse behavior. I also checked the subprocess failure fallback, hook-exported Git environment scrubbing, and build portability across the release targets.

Open in Web View Automation 

Sent by Cursor Automation: pr-flow

@corgea-security

Copy link
Copy Markdown

@cursor[bot] Regarding #161 (review):

I agree with this finding and think it should be addressed.

high: Hidden worktree changes can reuse an unrelated clean scan

git_status_has_changes treats empty porcelain output as clean, but Git can intentionally hide modified files via assume-unchanged, skip-worktree, or status configuration. The scanner still packages the modified worktree bytes, while resolve_reusable_scan now reuses the prior scan of HEAD. Therefore the reused findings and blocking decision can describe different source than this invocation would scan. The new test explicitly codifies this incorrect reuse behavior. User-visible git status consistency is not sufficient for a security scanner's content-identity check.

Proof or reproduction:

git update-index --assume-unchanged main.py
printf "vulnerable_change\n" > main.py
test -z "$(git status --porcelain)"  # reported clean
corgea scan blast --skip-if-commit-scanned-recently ...
# Now prints CORGEA_SCAN_SKIPPED=true and gates on the old HEAD scan,
# although main.py contains bytes absent from that scan.

@corgea-security corgea-security left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review risk: 4/5.

The new Git-status-only dirtiness check can reuse a scan for source different from the current worktree, creating a security-scan bypass.

Critical or high-priority changes must be addressed.

Automatic approval was not submitted: checks failed: rust-tests.

@corgea-security corgea-security added the dennis-reviewed Dennis completed an automated review label Aug 23, 2026

@juangaitanv juangaitanv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

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

Labels

dennis-reviewed Dennis completed an automated review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants