-
Notifications
You must be signed in to change notification settings - Fork 3k
fix(review): screen content filters before the probe tree's restore too (#9558) #9566
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b8b27e5
0153c14
1964a18
5cf6f9e
e28e352
3b99ac6
55d2bfa
65edd02
a1cc128
b25670f
17ae6cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -56,8 +56,12 @@ import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js'; | |
| import { baseWorktreePath } from './lib/paths.js'; | ||
| import { | ||
| discardWorktree, | ||
| INERT_GIT_ARGS, | ||
| localFilterBreach, | ||
| localFilterRefusal, | ||
| sanitizedGitEnv, | ||
| worktreeCreateFailureDetail, | ||
| type LocalFilterBaseline, | ||
| type SweepResult, | ||
| } from './lib/worktree.js'; | ||
| import { runBuildTest, type BuildTestReport } from './build-test.js'; | ||
|
|
@@ -94,8 +98,11 @@ export interface BaseTreeArgs { | |
| // discovery for every call at once — the base tree would be added into the | ||
| // redirected repository and its reuse check would read HEAD from it, an A/B | ||
| // against the wrong program while every check against the given tree passes. | ||
| // The INERT_GIT_ARGS prefix is for the `worktree add` below: its initial | ||
| // checkout fires hooks and `core.fsmonitor` from the common dir otherwise, | ||
| // and a probe's plant lands exactly there. | ||
| function gitOut(cwd: string, ...args: string[]): string { | ||
| const r = spawnSync('git', args, { | ||
| const r = spawnSync('git', [...INERT_GIT_ARGS, ...args], { | ||
| cwd, | ||
| encoding: 'utf8', | ||
| env: sanitizedGitEnv(), | ||
|
|
@@ -108,7 +115,7 @@ function gitOut(cwd: string, ...args: string[]): string { | |
| } | ||
|
|
||
| function git(cwd: string, ...args: string[]): void { | ||
| const r = spawnSync('git', args, { | ||
| const r = spawnSync('git', [...INERT_GIT_ARGS, ...args], { | ||
| cwd, | ||
| encoding: 'utf8', | ||
| env: sanitizedGitEnv(), | ||
|
|
@@ -261,12 +268,67 @@ export function runBaseTree(args: BaseTreeArgs): BaseTreeReport { | |
| // The parameter re-narrows: TS narrowing does not cross function scopes. | ||
| function buildBaseTree(baseSha: string): BaseTreeReport { | ||
| let sweep: SweepResult | undefined; | ||
| // Hoisted above the try: the catch below runs the paired re-read when | ||
| // the add THREW, and a re-read without the baseline the screen | ||
| // captured is blind to every self-erasing shape. | ||
| const captured: { baseline: LocalFilterBaseline | null } = { | ||
| baseline: null, | ||
| }; | ||
| try { | ||
| // Clear a stale base tree left by a crashed run — it would fail `add`. Its | ||
| // stderr is kept, because it is usually what explains that failure. | ||
| sweep = discardWorktree(worktree, tree); | ||
| // BEFORE the checkout runs, directly beside it: the `worktree add` | ||
| // below executes whatever the screen detects — the same surface | ||
| // `scratch-tree` refuses to reset through, one directory over. The | ||
| // screen sits BELOW the sweep, not above it — a plant parked in the | ||
| // stale tree's own admin `config.worktree` is state that sweep just | ||
| // destroyed and the add never reads, and a screen that ran first | ||
| // refused on it forever: every retry re-screened the doomed state and | ||
| // wedged the repository out of every review (measured live). | ||
| const refusal = localFilterRefusal( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R9-1 (location 1 of 2; twin at fetch-pr.ts's screen): sweep→screen→add class at a NEW site — the sweep removes the tree's admin registration BEFORE the screen enumerates candidates, so the NEW tree's own Witness: execution premise measured three ways — GIT_TRACE shows Suggested fix: baseline the about-to-be-created tree's expected config.worktree as vanished before the add, or treat any 中文说明R9-1(共 2 处,第 1 处;孪生站点在 fetch-pr.ts 的屏蔽):sweep→screen→add 类的新站点——清扫在屏蔽枚举候选之前就移除了树的管理注册,因此新树自身的 证据:执行前提经三种方式实测——GIT_TRACE 显示 建议修复:在 add 之前把即将创建的树的预期 config.worktree 以 vanished 入基线;或把复读时存在但不在 baseline.files 中的任何 — qwen3.8-max via Qwen Code /review (v0.21.15)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to the next round — round cap. This round implemented an 8-finding batch within the round cap; this finding is queued for the next round rather than dropped. Queued with the R8-5 twins (scratch rebuild, probe creation) so all four sweep→screen→add sites get the baseline seed in one coherent change. 中文说明推迟到下一轮 —— 每轮数量上限。本轮在每轮上限内实现了 8 个 finding;该 finding 已排入下一轮队列,不会被丢弃。 已与 R8-5 同族项(scratch rebuild、probe creation)一起排队,使全部四个 sweep→screen→add 位置在一个一致的变更中获得基线播种。 |
||
| worktree, | ||
| 'the worktree add this command runs', | ||
| captured, | ||
| ); | ||
|
Comment on lines
+289
to
+293
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R5-2: This creation checkout is screened but has no paired post-checkout Witness (probe, this site): Pair this checkout with 中文说明这个创建 checkout 有前置屏蔽,但没有配对的 checkout 后 — qwen3.8-max via Qwen Code /review (v0.21.15)
Comment on lines
+289
to
+293
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] R5-23 (location 2 of 3): the base-tree 中文说明R5-23(共 3 处,第 2 处):base-tree 的 — qwen3.8-max via Qwen Code /review (v0.21.15)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to the next round: batch capped at the Critical findings. Still valid — same residue-note gap in base-tree's build-gate failure path; queued with R5-22/R5-23. 推迟到下一轮:本轮批次以 Critical 发现为上限。仍然有效——base-tree 构建门失败路径存在同样的残留物注记缺口;与 R5-22/R5-23 一起排队。 |
||
| if (refusal !== null) return unavailable(refusal); | ||
| git(worktree, 'worktree', 'add', '--detach', tree, baseSha); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R8-7 (location 3 of 5): the base-tree add has no placement re-check between its screen and this add. The base tree's path shares the ancestor .qwen/tmp with the review worktree (paths.ts builds -base); an attacker with write access to .qwen — the exact threat model this PR's breach tests defend against — swaps that ancestor to a dir holding a consistent copy of the review worktree whose .git gitfile points back at the real common dir, so the screen and the baseline's identity both resolve clean through the copy, and this add then checks out into the attacker dir. Auditor probed live: git worktree list afterwards records the attacker-dir realpath; the breach re-read answers clean (identity compares the unchanged common dir; changedScreenedFiles stats only baselined config files); pre-planted untracked files the checkout will not remove (e.g. a .npmrc aiming npm at an attacker registry) are what the immediately following npm ci build then executes inside. Fix: between the screen and this add, walk the target and its ancestors within the stop boundary and refuse on any symlink. 中文说明R8-7(共 5 处,第 3 处):base-tree 的 add 在屏蔽与这个 add 之间没有放置复核。base 树路径与 review 工作树共享祖先 .qwen/tmp(paths.ts 构造 -base);对 .qwen 有写权限的攻击者——本 PR 的 breach 测试所防御的那个威胁模型——把该祖先替换为一个目录,其中放着 review 工作树的一致副本、其 .git gitfile 指回真实 common 目录,于是屏蔽与基线身份都透过副本解析为干净,而这个 add 会把 checkout 写进攻击者目录。审计者实测:事后 git worktree list 记录的是攻击者目录的 realpath;breach 复读回答干净(身份比较的是未改变的 common 目录;changedScreenedFiles 只 stat 入基线的配置文件);攻击者预植的、checkout 不会删除的未跟踪文件(例如把 npm 指向攻击者 registry 的 .npmrc)会在紧随其后的 npm ci 构建中被执行。修复:在屏蔽与这个 add 之间,对停止边界内的目标及其祖先做遍历,出现任何符号链接即拒绝。 — qwen3.8-max via Qwen Code /review (v0.21.15)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to the next round — round cap. This round implemented an 8-finding batch within the round cap; this finding is queued for the next round rather than dropped. Placement re-validation (an ancestor symlink swapped between the screen and the add) spans five sites; it is queued as one batch so the shared helper and its witnesses land together. 中文说明推迟到下一轮 —— 每轮数量上限。本轮在每轮上限内实现了 8 个 finding;该 finding 已排入下一轮队列,不会被丢弃。 放置位置再验证(在 screen 与 add 之间交换祖先符号链接)横跨五个位置;已作为一个批次排队,使共享辅助函数与其见证测试一起落地。 |
||
| // Re-read AFTER the checkout, paired with the screen above: the | ||
| // screen is a point-in-time read, and a concurrent writer the | ||
| // pipeline itself schedules (a sibling shard's suite, a toggler the | ||
| // reap did not reach) can plant between the two reads — the add's | ||
| // initial checkout executes the plant while both reads see nothing. | ||
| // A key that APPEARED — or a screened file that CHANGED, the trace | ||
| // a self-erasing plant leaves when its unset lands before this | ||
| // re-read — is reported as a breach and the just-added tree rolled | ||
| // back: the run is never certified clean with a REPO-LOCAL plant | ||
| // behind it. A filter defined only in the GLOBAL config is the | ||
| // screen's disclosed limit and leaves no repo-local trace the | ||
| // re-read can see. | ||
| const breach = localFilterBreach( | ||
| worktree, | ||
| 'the worktree add this command ran', | ||
| captured.baseline, | ||
| ); | ||
| if (breach !== null) { | ||
| discardWorktree(worktree, tree); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Critical] R9-10 (location 3 of 3; twins: fetch-pr.ts:999, test-efficacy.ts:1647): the breach-rollback call runs discardWorktree — whose git spawns (worktree remove --force, unlock, remove --force --force, plus dropWorktreeRegistration's rev-parse fallback) carry NO timeout — BEFORE returning the already-computed breach report. A FIFO planted where git's config read follows it blocks the rollback forever: the detected breach is never reported, the command hangs, and the finally rmSync(lock) never runs — the build lock leaks until the 30-minute stale sweep. Witness: probe — with /config replaced by a FIFO, Suggested fix: give discardWorktree's spawns 中文说明R9-10(共 3 处,第 3 处;孪生:fetch-pr.ts:999、test-efficacy.ts:1647):breach 回滚调用在返回已经算好的 breach 报告之前运行 discardWorktree——其 git spawn(worktree remove --force、unlock、remove --force --force,外加 dropWorktreeRegistration 的 rev-parse 回退)都没有 timeout。植入在 git 配置读取会跟随之处的 FIFO 会永久阻塞回滚:已检测到的 breach 永不被报告、命令挂起、finally 的 rmSync(lock) 永不执行——构建锁泄漏直到 30 分钟的陈旧清扫。 证据:探针——/config 被换成 FIFO 时, 建议修复:给 discardWorktree 的 spawn 加 — qwen3.8-max via Qwen Code /review (v0.21.15)
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Deferred to the next round — round cap. This round implemented an 8-finding batch within the round cap; this finding is queued for the next round rather than dropped. Queued with the R8-8 timeout batch (the rollback / runRestore / discardWorktree spawns). 中文说明推迟到下一轮 —— 每轮数量上限。本轮在每轮上限内实现了 8 个 finding;该 finding 已排入下一轮队列,不会被丢弃。 已与 R8-8 超时批次一起排队(rollback / runRestore / discardWorktree 的 spawn)。 |
||
| return unavailable(breach); | ||
| } | ||
| } catch (e) { | ||
| // A checkout that THREW may still have executed a plant first — a | ||
| // smudge that kills git mid-checkout fires and only THEN makes the | ||
| // spawn throw — and the failure detail below would bury the | ||
| // execution under an add error. Attribute it while the baseline the | ||
| // screen captured still stands; the next call's screen may read | ||
| // clean, the plant having erased itself. | ||
| if (captured.baseline !== null) { | ||
| const breach = localFilterBreach( | ||
| worktree, | ||
| 'the worktree add this command ran', | ||
| captured.baseline, | ||
| ); | ||
| if (breach !== null) return unavailable(breach); | ||
| } | ||
| return unavailable( | ||
| worktreeCreateFailureDetail('base', e, String(sweep?.stderr ?? '')), | ||
| ); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] R8-8 (deadline aggregate, location 4 of 5): both git helpers this diff retouches (INERT_GIT_ARGS prefix) spawn with no timeout — the guarded worktree add and the reuse fast-path rev-parse HEAD. Witness (probe, both arms): arm 1 — global-config stall filter (the screen's disclosed limit) + committed attributes: the screen clears, the add blocks the smudge's entire duration (elapsed_ms=30055 for a call that otherwise takes <1s; sleep infinity never returns — event loop stalled in spawnSync, the paired breach re-read never runs, no report at all); arm 2 — reuse fast path with include.path = : runBaseTree never returns; a mid-hang process sample caught the exact spawn git -c core.hooksPath=… -c core.fsmonitor= rev-parse HEAD in wchan=wait_for_partner (FIFO open wait); watchdog rc=124. Fix: timeout: GIT_TIMEOUT_MS on both helpers; a timeout kill then throws through the existing r.error path, so the catch's paired breach re-read still runs.
中文说明
R8-8(期限聚合,共 5 处,第 4 处):本 diff 触碰过的两个 git 助手(加 INERT_GIT_ARGS 前缀)的 spawn 都没有 timeout——受守护的 worktree add 与复用快路径的 rev-parse HEAD。证据(探针,两臂):臂 1——全局配置卡死过滤器(屏蔽的披露限制)+ 已提交的 attributes:屏蔽干净通过,add 阻塞整个 smudge 时长(一次本应 <1 秒的调用 elapsed_ms=30055;sleep infinity 则永不返回——事件循环卡在 spawnSync,成对 breach 复读永不运行,完全没有报告);臂 2——复用快路径 + include.path = :runBaseTree 永不返回;挂起中的进程采样恰好抓到 git -c core.hooksPath=… -c core.fsmonitor= rev-parse HEAD 处于 wchan=wait_for_partner(等待 FIFO 打开);看门狗 rc=124。修复:两个助手都加 timeout: GIT_TIMEOUT_MS;超时杀掉会经既有 r.error 路径抛出,catch 中的成对 breach 复读因此仍能运行。
— qwen3.8-max via Qwen Code /review (v0.21.15)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deferred to the next round — round cap. This round implemented an 8-finding batch within the round cap; this finding is queued for the next round rather than dropped. The GIT_TIMEOUT_MS deadline for the timeout-less spawns is queued as one batch (mechanical but each site needs its own FIFO witness) so the fixes and their witnesses land together.
中文说明
推迟到下一轮 —— 每轮数量上限。本轮在每轮上限内实现了 8 个 finding;该 finding 已排入下一轮队列,不会被丢弃。 为无超时的 spawn 添加 GIT_TIMEOUT_MS 截止时间已作为一个批次排队(改动是机械性的,但每个位置都需要自己的 FIFO 见证),使修复与见证一起落地。