跨端: AGC 闹钟**只枚举一条 ref** 而声称"整个远端" —— 改成对全部远端 ref 判
pi 2026-09-15 抓到的**第三个洞**,而且他说得对:这是**同一句话换一根轴**。
## 一、洞:我在"可达性"那侧立了"问不到 ⇒ 红",却在"覆盖面"那侧"看不到 ⇒ 绿"
```js
const branch = rb ? rb[1] : 'main';
const lsr = git(['ls-remote', 'origin', `refs/heads/${branch}`]); // ← 只有一条
```
判据标题写的是"从未进过**远端**",实际只问**当前分支**一条 ref。于是
**把受污染历史推到旁支、或推一个指向它的 tag** ⇒ 凭证出去了,而这条判据**照样绿**。
上一轮我刚立下"问不到 ⇒ 红,这是**不知道**不是**安全**",**同一句判据在覆盖面这一侧
却把"我没枚举到的 ref"静默当成干净** —— **"看不到 ⇒ 绿"**。
和"把不知道读成安全"是同一句话,只是**宾语换成了覆盖面**而不是可达性。
## 二、修法(pi 给的,**不需要 fetch 任何对象**)
`ls-remote` 问**全部 ref**,对**每一条**的 tip 查该路径是否在其可达历史里。
查明"远端有哪些 ref"是 `ls-remote` 的免费信息,没有理由只问一条。
`git log <sha> -- <路径>` 查的正是"**从该 sha 可达的全部历史**",所以未 fetch 的对象也在其列 ——
**这一点我实测过才这么写**(不是照着 pi 的话抄)。
## 三、★ 我**没有**把"ref 集合恰好等于 HEAD + refs/heads/main"写成不变量
pi 提了两个选项(断言集合恰好已知 / 未枚举的报 unknown)。我选第三条:
**判"有没有哪条 ref 的可达历史里有那个 blob"** —— 它对**任意** ref 集合都成立。
理由是 `pre-push` 删 ref 那次的教训:**一道闸消费了不属于它管辖的东西**。
把"远端只有一条分支"写成不变量,会在**加第一个 tag / 第一条正常旁支**时误红 ——
那是**为了抓泄露而给日常操作设卡**。要判的性质是"泄露",不是"ref 集合的形态"。
## 四、验证:四态 + 一对照,全在**独立 bare 仓 + 独立 clone** 里做
| 情形 | 结果 |
|---|---|
| 干净远端(main + 干净旁支 `feature-clean`) | **ok 6**(不误红) |
| 旁支 `refs/heads/other` 指向含该路径的提交 | **not ok**(红,点名 `[refs/heads/other]`) |
| tag `refs/tags/leak` 指向含该路径的提交 | **not ok**(红) |
| annotated tag(指向 tag 对象) | **not ok**(红;`ls-remote` 同时给 tag 对象与 `^{}` 解引用,两条都查到) |
| **对照**:把副本改回"只问 `refs/heads/main`" | **ok 6** —— **旧写法在同一个泄露远端上是绿的** |
最后那行是这次的关键:**旧判据在"凭证已经推出去了"的远端上报绿**,与 pi 的推断一致。
`unresolved` 那一支(`git log <sha>` 解析不了的 ref)也留着了:**报 unknown 红,不当绿** ——
**查不了 ≠ 干净**。
## 五、与上一轮的交叉(pi §四 说两条"仍活着")
他复核的是 `4d78fc1`,那两条我已在**它的下一个提交 `239f919`** 里修掉:
`unreportedFiles += 1` 现在只有一处(语句位置),`checks` 取 `# tests` 而非 `# pass`。
现测:`checks=400 pass=399 fail=1 red=4` —— 与他算的 400/399 逐字一致。**内容以 `239f919` 为准。**
This commit is contained in:
@ -401,42 +401,74 @@ test('★ AGC 真身从未进过远端历史(泄露之后响的闹钟,不是
|
||||
* 一致 ⇒ 本地历史可信,判它
|
||||
* 不一致 ⇒ **红**(本地 ref 陈旧或被改过 —— 这种时候"绿"毫无意义)
|
||||
* 问不到 ⇒ **红并明说**"这是不知道,不是安全"
|
||||
*
|
||||
* ★★ 而**覆盖面**也要按同一句话判(pi 2026-09-15 抓到的第三个洞):
|
||||
* 我原来只问 `refs/heads/<当前分支>` **一条 ref**,而这条判据的标题说的是
|
||||
* "从未进过**远端**" —— 于是**一次把受污染历史推到旁支、或推一个指向它的 tag**,
|
||||
* 凭证就出去了,而这条判据**照样绿**。
|
||||
* ⇒ 这就是"**把'不知道'读成'安全'**"的**同一句话换一根轴**:
|
||||
* 可达性那一侧我立了"问不到 ⇒ 红",**覆盖面**这一侧却把
|
||||
* "我没枚举到的 ref"**静默当成干净** —— **"看不到 ⇒ 绿"**。
|
||||
*
|
||||
* 修法(pi 给的,**不需要 fetch 任何对象**):`ls-remote` 问**全部 ref**,
|
||||
* 对**每一条**的 tip 都查该路径是否在其可达历史里。查明"远端有哪些 ref"是
|
||||
* `ls-remote` 的免费信息,没有理由只问一条。
|
||||
*
|
||||
* ⚠️ 我**没有**把"远端 ref 集合恰好等于 HEAD + refs/heads/main"写成不变量 ——
|
||||
* 那会在加第一个 tag / 第一条正常旁支时误红(**为了抓泄露而给日常操作设卡**,
|
||||
* 与 `pre-push` 删 ref 那次同族:**一道闸消费了不属于它管辖的东西**)。
|
||||
* 真正要判的性质是"**有没有哪条 ref 的可达历史里有那个 blob**",
|
||||
* 它对**任意** ref 集合都成立。
|
||||
*/
|
||||
const rb = /refs\/remotes\/origin\/(\S+)/.exec(git(['symbolic-ref', '-q', 'refs/remotes/origin/HEAD']).stdout.trim());
|
||||
const branch = rb ? rb[1] : 'main';
|
||||
const lsr = git(['ls-remote', 'origin', `refs/heads/${branch}`]);
|
||||
const lsr = git(['ls-remote', 'origin']);
|
||||
assert.equal(lsr.status, 0,
|
||||
`问不到远端(\`git ls-remote origin refs/heads/${branch}\` 失败)——\n`
|
||||
'问不到远端(`git ls-remote origin` 失败)——\n'
|
||||
+ ' ★ 这是**不知道**,不是**安全**。一条专门用来抓"绕过"的闹钟,\n'
|
||||
+ ' 如果因为"不 fetch / 问不到"就报绿,那它自己就能被绕过。\n'
|
||||
+ ' 修法:确认远端可达、`origin` 名字对,再跑这条。');
|
||||
const remoteTip = (lsr.stdout.trim().split('\n')[0] || '').split('\t')[0];
|
||||
assert.ok(/^[0-9a-f]{40}$/.test(remoteTip),
|
||||
`远端没有 refs/heads/${branch}(或输出形状不认识)—— 同样按"不知道"处理,不当绿。`
|
||||
/*
|
||||
* 解析成 {ref, sha}。跳过 `HEAD`(symbolic,与某条分支同 sha,查它是重复劳动);
|
||||
* 形状不认识的直接跳过(下面 `remoteRefs.length > 0` 会兜住"全都没认出来")。
|
||||
*/
|
||||
const remoteRefs = [];
|
||||
for (const line of lsr.stdout.trim().split('\n')) {
|
||||
const [sha, ref] = line.split('\t');
|
||||
if (!sha || !ref || ref === 'HEAD') continue;
|
||||
if (!/^[0-9a-f]{40}$/.test(sha)) continue;
|
||||
remoteRefs.push({ ref, sha });
|
||||
}
|
||||
assert.ok(remoteRefs.length > 0,
|
||||
'远端一条 ref 都没解析出来(或输出形状不认识)—— 按"不知道"处理,不当绿。'
|
||||
+ `\n 原始输出:${JSON.stringify(lsr.stdout.slice(0, 200))}`);
|
||||
|
||||
const localTip = git(['rev-parse', `refs/remotes/origin/${branch}`]);
|
||||
assert.equal(localTip.status, 0,
|
||||
`本地没有 refs/remotes/origin/${branch} —— 无从证明本地历史等于远端,别把"没有"读成"干净"`);
|
||||
const localSha = localTip.stdout.trim();
|
||||
assert.equal(localSha, remoteTip,
|
||||
'本地 `origin/' + branch + '` 与**远端真值不一致** ——\n'
|
||||
+ ` 本地:${localSha}\n 远端:${remoteTip}\n`
|
||||
+ ' ⇒ 本地那条历史**此刻不可信**,而这条判据要读的正是它。\n'
|
||||
+ ' ★ 这种时候报"绿"是没有意义的(**"ref 被改坏了"与"它其实被推过"是同一个盲区** ——\n'
|
||||
+ ' 我在做变体验证时就亲手把这条 ref 指到过伪造的提交上)。\n'
|
||||
+ ' 修法:`git fetch origin` 把它拉回真值,再跑。');
|
||||
/*
|
||||
* 对**每一条**远端 ref 的 tip 查该路径。`git log <sha> -- <路径>` 查的是
|
||||
* **从该 sha 可达的全部历史**,所以未 fetch 的对象也在其列(这一点我实测过才这么写)。
|
||||
* 解析不了的(annotated tag 指向 tag 对象等)**报 unknown 而不是绿**。
|
||||
*/
|
||||
const published = [];
|
||||
const unresolved = [];
|
||||
for (const { ref, sha } of remoteRefs) {
|
||||
const lg = git(['log', '--oneline', sha, '--', AGC_PATH]);
|
||||
if (lg.status !== 0) { unresolved.push(`${ref}(${sha.slice(0, 8)})`); continue; }
|
||||
if (lg.stdout.trim() !== '') {
|
||||
published.push(`[${ref}] ${sha.slice(0, 8)}\n`
|
||||
+ lg.stdout.trim().split('\n').map(l => ' ' + l).join('\n'));
|
||||
}
|
||||
}
|
||||
assert.deepEqual(unresolved, [],
|
||||
'这几条远端 ref 的 tip **查不了可达历史**(`git log <sha> -- <路径>` 失败)——\n'
|
||||
+ ' ★ 按"不知道"处理,**不当绿**:**查不了 ≠ 干净**。\n'
|
||||
+ ' (常见成因:annotated tag 指向 tag 对象,或该对象本地没有且远端也不可达。)\n'
|
||||
+ ` ${unresolved.join('\n ')}`);
|
||||
|
||||
const published = git(['log', `origin/${branch}`, '--oneline', '--', AGC_PATH]);
|
||||
assert.equal(published.status, 0,
|
||||
'要能查到 origin/main —— 查不到时不要当成"没有"(那是把"不知道"读成"安全")');
|
||||
assert.equal(published.stdout.trim(), '',
|
||||
'AGC 客户端凭证的**真身已经进过远端历史** ——\n'
|
||||
assert.deepEqual(published, [],
|
||||
`AGC 客户端凭证的**真身已经进过远端历史**(在 ${published.length} 条 ref 上查到)——\n`
|
||||
+ ' 它含 `client_secret`/`api_key` 信封与明文 `client_id`/`app_id`,\n'
|
||||
+ ' 而本仓镜像是**匿名可 clone 的公开项目**(docs/GITCODE-MIRROR.md)。\n'
|
||||
+ ' ★ 处置**不是**"删掉再推"(历史里的 blob 撤不回):按**已泄露**处理 ——\n'
|
||||
+ ' 1) 去 AGC 控制台**轮换** client_secret / api_key;\n'
|
||||
+ ' 2) 历史清理(filter-repo)+ 强推,并与所有 clone 过的人对齐;\n'
|
||||
+ ' 3) 查清是哪次 push 绕过了 `.githooks/pre-push`(`--no-verify`?没装的机器?)。\n'
|
||||
+ ` 涉及提交:\n ${published.stdout.trim().split('\n').join('\n ')}`);
|
||||
+ ` 命中:\n ${published.join('\n ')}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user