From fde3119916edc47a1c89ff1b621d49a11e005797 Mon Sep 17 00:00:00 2001 From: wenshao Date: Sat, 29 Aug 2026 00:27:48 +0800 Subject: [PATCH 01/15] fix(review): screen content filters before the probe tree's restore too (#9558) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `scratch-tree` refuses to create or reset a tree while the repository's LOCAL config defines a content filter, because a checkout executes `filter..smudge` whenever it rewrites a file and disabling hooks does not cover that. `test-efficacy`'s per-run restore is the same checkout one directory over — it rewrites every tracked file in the probe tree, twice per probe run — and screened nothing. The screen moves to `lib/worktree.ts` so both callers share one implementation rather than one growing a corner the other lacks, which is how the `config.worktree` scoping bug got in. `filter..process` joins `smudge` and `clean`: it is the third executable key — a long-running filter git speaks a protocol to — and enumerating two of three is how the first cut read as complete. **Repo-local only, and that is the load-bearing part.** `git lfs install` writes `filter.lfs.clean` into the user's GLOBAL config, so a screen over merged config would put every contributor with git-lfs into permanent refusal — the same failure as a tripwire that fires on every healthy run, which this pipeline has already shipped once. Both halves are pinned: removing the screen turns the new test red, and widening it to merged config turns the same test red from the other side. --- .../cli/src/commands/review/lib/worktree.ts | 74 +++++++++++++++++++ .../src/commands/review/scratch-tree.test.ts | 12 ++- .../cli/src/commands/review/scratch-tree.ts | 72 +----------------- .../src/commands/review/test-efficacy.test.ts | 61 +++++++++++++++ .../cli/src/commands/review/test-efficacy.ts | 13 ++++ 5 files changed, 160 insertions(+), 72 deletions(-) diff --git a/packages/cli/src/commands/review/lib/worktree.ts b/packages/cli/src/commands/review/lib/worktree.ts index 13a122da70f..aa6db6df8de 100644 --- a/packages/cli/src/commands/review/lib/worktree.ts +++ b/packages/cli/src/commands/review/lib/worktree.ts @@ -578,6 +578,80 @@ function trackedIgnoreSources( */ export const RESIDUE_PATH_CAP = 12; +/** + * The repo-local `filter.` COMMANDS, when any are defined. + * + * Every checkout in this pipeline EXECUTES these — the scratch tree's reset and + * rebuild, and the probe tree's per-run restore — hooks being disabled covers + * hooks and not filters. The planting surface is two plain writes a probe can + * make into the COMMON dir this command's report calls shared: + * `git config filter.evil.smudge CMD` and one line appended to + * `$(git rev-parse --git-path info/attributes)`. discard and cleanup never + * wipe the common dir, so a filter planted while reviewing one PR fires on + * every later matching checkout of the user's OWN repository — persistence + * planted by reviewing a malicious PR, measured live. The two local config + * files are checked with `--file` rather than merged config because filters + * in the user's global config (git-lfs is the common one) are the user's own + * contract, exactly like any git command they run — while a probe's planting + * surface is the repo-local files. The state cannot be told apart from a + * filter the user set deliberately, and cannot be safely wiped, so a hit is a + * refusal upstream, not a cleanup here. + */ +export function localFilterCommands(worktree: string): string[] { + const files = spawnSync( + 'git', + ['rev-parse', '--git-common-dir', '--git-dir'], + { cwd: worktree, encoding: 'utf8', env: sanitizedGitEnv() }, + ); + if (files.error || files.status !== 0 || typeof files.stdout !== 'string') { + return []; + } + const [commonDir, gitDir] = files.stdout.trim().split('\n'); + const common = resolve(worktree, commonDir); + const candidates = [ + join(common, 'config'), + join(resolve(worktree, gitDir), 'config.worktree'), + ]; + // Every OTHER worktree's per-worktree config too. This screen runs against + // the review worktree, but the checkout it authorises runs in the SCRATCH + // tree, whose own `/worktrees/