Skip to content

Commit c7c4ccb

Browse files
docs: the whole-history sweep this corpus has not done, and what it costs
Every one of the 2,225 rows reached the classifier because a keyword or an advisory found it first. There are no rows drawn from whole commit histories, the curated set covers 0.291% of the 595,966-commit history, and the per-client coverage spread is 7.3x — which is the same confound the sampling work already ran into. The sister wallet corpus moved from this exact position to reading histories end to end and measured the cost of the keyword route at 81% of fixes missed, so the gap here is probably comparable. docs/TODO.md sequences the work and puts numbers on it. The screen has to be scored against the 300-commit external validation set before anything is swept: at recall 32.7% and an 18.7% positive rate on random commits, sweeping now would waste most of what it costs. That check is 0.4-0.7M tokens; the first client (grandine) is 5M; the four smallest are 96M batched, 171M as one call per commit. Measured from the wallet caches: 2,150 input and 77 output tokens per judgement, 57% of the input being a static prompt that batching would amortise. It also records the traps the wallet sweep hit after publishing, so they get built in rather than discovered: reachability and squash-duplicate flags (892 of 5,457 rows never shipped, 475 were the same fix twice), release-tag verification for any row shown as an example (3 of the first 5 candidates failed it), and no patch-id backport detection, which did not survive testing as a signal.
1 parent 5249b6e commit c7c4ccb

2 files changed

Lines changed: 142 additions & 0 deletions

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,18 @@ recover low-disclosure fixes (full write-up:
117117
rate-limit-free from bare git clones by `collection/local_diffs.py`. This pass
118118
admitted **+453** silent fixes the deterministic gate had missed.
119119

120+
**What this corpus does not yet do.** Every row reached the classifier because a
121+
keyword or an advisory found it first — there are no rows drawn from whole
122+
commit histories (`contest == "all-commits"` is empty), and the curated set
123+
covers 0.291% of the 595,966-commit history with a 7.3x per-client spread. The
124+
sister project [`wallet-vuln-dataset`](https://github.com/NyxFoundation/wallet-vuln-dataset)
125+
moved from this same position to reading histories end to end and measured what
126+
the keyword route costs: **81% of the fixes it recovered carry none of the
127+
crawler's search terms.** Closing that gap here is planned but blocked on the
128+
screen, which fails external validation (recall 32.7%, positive rate on random
129+
commits 18.7%). The sequence, the pass/fail bar and the measured token cost are
130+
in [`docs/TODO.md`](docs/TODO.md).
131+
120132
## Severity & scope — the bug-bounty model
121133

122134
Severity here follows the **[Ethereum Foundation bug bounty](https://ethereum.org/en/bug-bounty/)**,

docs/TODO.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# TODO — 全履歴からの収集(キーワード非依存スイープ)
2+
3+
現状の 2,225 行は、キーワード検索とアドバイザリ経由だけで集まっている。
4+
全変更履歴から引っ張った行は 1 件もない(`contest == "all-commits"` が 0 件)。
5+
姉妹プロジェクト [`wallet-vuln-dataset`](https://github.com/NyxFoundation/wallet-vuln-dataset)
6+
で同じ状態から全履歴読解に移した結果、**キーワード方式は同じ母集団の 81% を取り落とす**
7+
と実測できた。ここでも同じ穴が空いている可能性が高い。
8+
9+
作業は Ollama Cloud で 1 クライアントずつ流す想定。以下は着手順に並べてある。
10+
11+
---
12+
13+
## 前提の確認(コストゼロ)
14+
15+
- [ ] `scratchpad_crawl/` は gitignore されており、**クライアントのクローンが手元にない**
16+
`collection/local_diffs.py warm --client <name>` で 11 個の blobless クローンを作り直す。
17+
標本枠 `data/commit_frame.parquet`(重複排除済み 595,966 コミット)は checked in なので、
18+
列挙のやり直しは不要。
19+
- [ ] 判定は diff を読む設計になっている(`collection/llm_classify_fixes.py``_diff_doc`
20+
6,000 字上限)。ここは正しいので触らない。直すのは候補の作り方だけ。
21+
22+
## Step 1 — 掃く前に screen を採点する(0.4〜0.7M トークン)
23+
24+
**これを飛ばしてはいけない。** 現行 screen は外部ラベルで落ちている:
25+
26+
| || 95% 区間 |
27+
|---|---:|---|
28+
| 既知の脆弱性修正に対する再現率 | 32.7% | 25.7–40.5 |
29+
| ランダムコミットへの陽性率 | 18.7% | 13.2–25.7 |
30+
| リフト | 1.75× | 区間がほぼ接触 |
31+
32+
3 分の 2 を取り落としながらランダムの 5 件に 1 件を陽性と言う状態なので、この screen で
33+
10 万コミットを掃くと投じたトークンのほとんどが無駄になる。
34+
35+
- [ ] `data/commit_classifier_validation.csv`(300 件。MineBlockVuln 由来の Geth 脆弱性修正
36+
150 + ランダム 150)に対して候補 screen を採点する。**このラベル付き検証セットが
37+
wallet 側には無かった資産**で、掃く前に安く判断できる唯一の道具。
38+
- [ ] 最初の候補は wallet で現用の分類器(`glm-5.2` + diff プロンプト)をそのまま当てる。
39+
移植コストがほぼゼロで、上回るかどうかだけ分かる。
40+
- [ ] 合格条件を先に決めておく: **再現率が 32.7% を有意に上回り、かつランダム陽性率が下がる**こと。
41+
再現率だけ上げてもランダム陽性率が付いてくるなら、リフトは改善していない。
42+
- [ ] 落ちた場合はプロンプトを直して再採点。**掃くのはここを通ってから。**
43+
44+
## Step 2 — grandine 1 クライアントだけ掃く(約 5M トークン)
45+
46+
- [ ] `wallet-vuln-dataset/collection/enumerate_commits.py` を移植する。構造的条件だけで絞る
47+
(マージ・非ソース・60 ファイル超の除外)。キーワードは一切使わない。
48+
patch-id によるバックポート検出は**入れない** — wallet で信号として撤回済み
49+
(リフト 1.74/0.94/0.45 で方向性なし)で、90 秒の予算でも大半のリポジトリでタイムアウトする。
50+
- [ ] grandine(3,112 コミット → 構造フィルタ後およそ 2,400 判定)を掃く。
51+
- [ ] 実測する: 収穫率、誤検出率、1 判定あたりの実トークン。
52+
wallet の実測値(下表)とずれるならそこで見積もりを直す。
53+
54+
## Step 3 — 収穫率を見てから残りを判断する
55+
56+
- [ ] grandine の結果で費用対効果が成り立つなら nimbus → lodestar → lighthouse。
57+
小さい順かつ現行カバー率が高い順で、キーワードの穴が測りやすい。
58+
- [ ] 1 クライアントずつ。wallet で並列実行はクォータの取り合いになり時間を失った。
59+
60+
## Step 4 — 掃いた行を取り込む(wallet で踏んだ穴を先に塞ぐ)
61+
62+
wallet では取り込み後にこれらが判明した。最初から入れておく。
63+
64+
- [ ] `merge_keywordless.py` 相当を用意する。スイープの出力を放置すると、
65+
「完了したのにデータセットに入っていない」状態になる。
66+
- [ ] **到達性と重複のフラグを最初から立てる**`mark_reachable.py` 相当)。
67+
`git log --all` は未マージ枝も拾い、squash マージされた PR は同じ修正を 2 回数える。
68+
wallet ではスイープ 5,457 件のうち **892 件がどのリリースにも届いておらず、475 件が重複**で、
69+
公開後に 2 割の過大計上を訂正することになった。列は `on_default` / `in_release` /
70+
`dup_subject` で、判定できない行は False ではなく **NA** にする。
71+
- [ ] `check_shipped.py` 相当で、実例として出す行は
72+
「既定ブランチ上」かつ「親を含み本人を含まないリリースタグが存在する」ことを確認する。
73+
これを通さないと、開発者が自分の枝を片付けただけのコミットと区別できない。
74+
wallet では最初に選んだ実例 5 件のうち 3 件がこの検証で落ちた。
75+
- [ ] `measure_keyword_blindspot.py` 相当で、掃いた結果に対してキーワード方式の取り落とし率を測る。
76+
wallet は 81%。ここでの値がこのプロジェクトの主要な結果になる。
77+
78+
---
79+
80+
## 実測トークン(wallet の diff キャッシュ 1.18GB・判定 166,388 件から測定)
81+
82+
| | 実測 |
83+
|---|---:|
84+
| 静的プロンプト | 4,411 文字 |
85+
| diff(6,000 字上限適用後) | 中央値 1,646 / 平均 2,624 / p90 6,000 文字 |
86+
| 入力 | 約 2,150 tok / 判定 |
87+
| 出力(判定 JSON、中央値 270 文字) | 約 77 tok / 判定 |
88+
89+
| 対象 | 判定数 | 1 件ずつ | 5 件バッチ |
90+
|---|---:|---:|---:|
91+
| 検証セットのみ | 300 | 0.7M | 0.4M |
92+
| grandine | 2,400 | 5.3M | 3.0M |
93+
| 第 1 波(grandine・nimbus・lodestar・lighthouse) | 76,787 | 171M | 96M |
94+
| 全 11 クライアント | 458,894 | 1,021M | 572M |
95+
96+
判定数は構造フィルタ後(wallet 実測で 77% 残存)。
97+
98+
- [ ] **バッチ化で 44% 減る。** 入力の 57%(1,225 tok)が毎回同じ静的プロンプトで、
99+
現行の分類器は 1 コミット 1 コール。`classify_stride_cwe.py` は既にバッチ化してあるので、
100+
同じ仕組みを `llm_classify_fixes.py` に持ち込む。掃く前にやる価値がある。
101+
102+
### クライアント別の規模
103+
104+
| クライアント | コミット | 判定コスト(50 行/分) | 現行カバー率 |
105+
|---|---:|---:|---:|
106+
| grandine | 3,112 | 1.0h | 0.611% |
107+
| nimbus | 25,142 | 8.4h | 1.070% |
108+
| lodestar | 32,639 | 10.9h | 0.689% |
109+
| lighthouse | 38,831 | 12.9h | 0.559% |
110+
| besu | 44,033 | 14.7h | 0.254% |
111+
| teku | 45,593 | 15.2h | 0.270% |
112+
| geth | 55,257 | 18.4h | 0.737% |
113+
| nethermind | 68,131 | 22.7h | 0.191% |
114+
| prysm | 77,628 | 25.9h | 0.146% |
115+
| reth | 93,360 | 31.1h | 0.196% |
116+
| erigon | 112,240 | 37.4h | 0.379% |
117+
| **** | **595,966** | **199h** | 0.291% |
118+
119+
カバー率は現コーパス(キーワード経由)の行数 ÷ コミット数。**0.146%〜1.070% で 7.3 倍の開き**
120+
あり、これがクライアント間比較を交絡させている。
121+
122+
---
123+
124+
## やらないこと
125+
126+
- **screen が検証を通る前に掃かない。** Step 1 を飛ばすと 96M トークンの大半が無駄になる。
127+
- **3,000 件標本から出た 16.9%(security-relevant)と 91%(サイレント率)は報告しない。**
128+
screen 自身の陽性バイアスに支配されている。撤回の経緯は `5249b6e` のコミットメッセージにある。
129+
- **クライアント別の率を標本から引用しない。** 最小層は grandine の n=15。
130+
- **patch-id によるバックポート検出は移植しない。** 信号として撤回済み。

0 commit comments

Comments
 (0)