Skip to content

Commit 65781c8

Browse files
docs: unify canonical dataset metrics
1 parent 04a4579 commit 65781c8

18 files changed

Lines changed: 313 additions & 135 deletions

README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ single schema, scored for security relevance, and tiered by evidence strength.
88
It is built for training and evaluating spec-compliance / audit tooling:
99
> *given the code state just before this fix, would the tool have caught the bug?*
1010
11-
Because Ethereum clients **silently patch** most vulnerabilities (no CVE, vague
12-
commit message), the hard part is separating real fixes from the flood of
11+
Because many Ethereum-client fixes lack a recognized advisory ID or a rated
12+
severity, the hard part is separating real fixes from the flood of
1313
refactors, dep-bumps and release notes. That separation — **the gate** — is what
1414
this README explains.
1515

@@ -34,8 +34,12 @@ renders as a table (key columns). Full data with inline pre/post code: [`ethereu
3434
| curated (security-only) | **2,225** |
3535
| └ essential slice (tier A ∪ B) | **1,808** |
3636
| by tier | A_authoritative 235 · B_corroborated 1,573 · C_candidate 417 |
37-
| by confidence | high 337 · medium 1,542 · low 454 |
38-
| by severity | Critical 3 · High 63 · Medium 60 · Low 21 · Info 853 · Unrated 1,333 |
37+
| by confidence | high 326 · medium 1,445 · low 454 |
38+
| by severity | Critical 3 · High 63 · Medium 57 · Low 20 · Info 750 · Unrated 1,332 |
39+
| public evidence | advisory ID 172 (7.7%) · rated 143 (6.4%) · neither 1,962 (88.2%) |
40+
41+
Canonical definitions and all current counts are fixed in
42+
[`docs/DATA_SNAPSHOT.md`](docs/DATA_SNAPSHOT.md).
3943

4044
## How the corpus is built
4145

@@ -99,8 +103,10 @@ Pre-gate de-noising, in order (`pipeline/build_security_dataset.py`):
99103

100104
## Silent-fix detection
101105

102-
Ethereum clients patch ~98–100% of vulnerabilities *silently*. Two research-backed
103-
methods recover them (full write-up: [`docs/silent_fix_detection.md`](docs/silent_fix_detection.md)):
106+
The current corpus has 1,962 records (88.2%) with neither a recognized
107+
CVE/GHSA/RustSec ID nor a rated severity. Two research-backed methods help
108+
recover low-disclosure fixes (full write-up:
109+
[`docs/silent_fix_detection.md`](docs/silent_fix_detection.md)):
104110

105111
- **Patch backlinking** — start from a confirmed advisory (OSV/GHSA) and extract
106112
the exact fixing commit/version (`fix_commit`). Deterministic, high precision.
@@ -128,13 +134,16 @@ The bounty's **scope is exactly this corpus** — the eleven clients plus
128134
`c-kzg-4844` and the deposit contract. Three consequences shape the `severity`
129135
column:
130136

131-
- **~94% of rows are `Unrated`.** Most fixes ship with no CVE or advisory, so the
132-
bug was never graded. **Unrated ≠ low impact** — the fix record, not the CVE
133-
list, is the map (see [`security_report.md`](docs/security_report.md)).
134-
- **Two severity models coexist.** A minority of rated rows are *upstream
135-
dependency CVEs* (log4j, Netty, `golang.org/x/crypto`) carrying **CVSS**
136-
severity — a dependency bug does not split the Ethereum network, so it is out of
137-
bounty scope. These are kept distinct from real client-bug (EF-bounty) grades.
137+
- **2,082 rows (93.6%) are `Info` or `Unrated`.** This is a rating-coverage
138+
statistic, not an advisory statistic. Under the canonical identifier search,
139+
**1,962 rows (88.2%) have neither a recognized advisory ID nor a rating**.
140+
**Unrated ≠ low impact** — the fix record, not a CVE score, is the primary
141+
evidence (see [`security_report.md`](docs/security_report.md)).
142+
- **Two severity models coexist.** Of the 143 rated rows, 60 are marked
143+
`bounty-graded` and 83 `upstream-cvss`. The latter label is not a verified
144+
dependency flag because the current heuristic also catches changelog/release
145+
rows; dependency scope requires manual review. Confirmed dependency CVEs carry
146+
CVSS and are outside the EF-bounty impact model.
138147
- **`severity_estimated` (optional LLM pass)** fills the gap by decomposing each
139148
fix into `impact_type` / `reachability` / `blast_radius` and mapping to the
140149
bounty tier, calibrated against the graded rows (exact-tier ~60% / ±1 ~80% on

data/manifest.json

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,50 +49,50 @@
4949
"security_rows": 2225,
5050
"low_signal_dropped": 14665,
5151
"by_confidence": {
52-
"medium": 1542,
52+
"medium": 1445,
5353
"low": 454,
54-
"high": 337
54+
"high": 326
5555
},
5656
"by_authority_tier": {
5757
"B_corroborated": 1573,
5858
"C_candidate": 417,
5959
"A_authoritative": 235
6060
},
6161
"by_n_signals": {
62-
"1": 487,
63-
"2": 1154,
64-
"3": 566,
62+
"1": 462,
63+
"2": 1104,
64+
"3": 533,
6565
"4": 120,
6666
"5": 6
6767
},
6868
"by_source": {
69-
"geth": 449,
70-
"erigon": 446,
71-
"nimbus": 272,
72-
"lodestar": 233,
73-
"lighthouse": 221,
74-
"reth": 197,
75-
"nethermind": 138,
69+
"erigon": 425,
70+
"geth": 407,
71+
"nimbus": 269,
72+
"lodestar": 225,
73+
"lighthouse": 217,
74+
"reth": 183,
75+
"nethermind": 130,
7676
"teku": 123,
77-
"besu": 117,
78-
"prysm": 114,
79-
"grandine": 21,
77+
"prysm": 113,
78+
"besu": 112,
79+
"grandine": 19,
8080
"consensus-specs": 2
8181
},
8282
"by_severity": {
83-
"Unrated": 1333,
84-
"Info": 853,
83+
"Unrated": 1332,
84+
"Info": 750,
8585
"High": 63,
86-
"Medium": 60,
87-
"Low": 21,
86+
"Medium": 57,
87+
"Low": 20,
8888
"Critical": 3
8989
},
9090
"by_score": {
9191
"0.0": 220,
92-
"0.3": 272,
93-
"0.5": 1208,
94-
"0.8": 296,
95-
"0.9": 170,
92+
"0.3": 271,
93+
"0.5": 1119,
94+
"0.8": 289,
95+
"0.9": 159,
9696
"1.0": 167
9797
},
9898
"residual_boilerplate_fp": 0,
@@ -106,4 +106,4 @@
106106
}
107107
},
108108
"source": "11 Ethereum execution + consensus clients (past security fixes)"
109-
}
109+
}

docs/BUILD_REPORT.md

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ Fresh crawl + deterministic curation. Raw snapshot re-collected from all 11
44
clients (+ consensus-specs) via `collection/run_pipeline.sh` (uncapped), then the
55
curated security-only set derived offline by `pipeline/build_security_dataset.py`.
66

7-
> **Note:** LLM STRIDE/CWE classification was **skipped** for this build, so
8-
> `stride=Other` / `cwe_top25=N/A` for every row. The GATE therefore keeps a row
9-
> only on an *independent non-LLM* signal: a CVE/GHSA id, a rated severity
10-
> (Critical/High/Medium/Low), or a security-keyword match (`security_score ≥ 0.5`).
11-
> This yields a smaller, higher-precision set than a classified build — the
12-
> ~16k rows dropped below are the unrated "stealth" fixes that only an LLM
13-
> STRIDE label would have admitted.
7+
> **Current-snapshot rule:** all “After” counts and coverage below are computed
8+
> from `data/ethereum_vulns.parquet` (2,225 rows). Iteration-local history lives
9+
> in [`IMPROVEMENT_LOG.md`](./IMPROVEMENT_LOG.md); canonical definitions and
10+
> hashes live in [`DATA_SNAPSHOT.md`](./DATA_SNAPSHOT.md).
1411
1512
## Before (raw crawl)
1613

@@ -49,44 +46,43 @@ Critical** (geth, besu, teku). Severities preserved through the canonical path.
4946
across all 11 clients (curated C_candidate + gate-dropped *plausible* rows),
5047
flagged **696** as real silent fixes. This both promotes classified fixes C→B
5148
and, via the gate, **admits +453 silent fixes the deterministic keyword gate
52-
had dropped** (1,880 → 2,333 rows). Model chosen by an 80-item eval sweep
49+
had dropped** in that intermediate iteration. Model chosen by an 80-item eval sweep
5350
(F1 0.872, precision 0.895); see `docs/model_evaluation.md`. Diffs served
5451
rate-limit-free by `local_diffs.py` (bulk PR-ref clone). Regenerate via
5552
`collection/llm_classify_fixes.py --apply``data/silent_fix_llm.csv`.
56-
- by severity: {'Unrated': 963, 'Info': 773, 'High': 63, 'Medium': 54, 'Low': 21, 'Critical': 3}
57-
(High/Medium dropped vs iter-1 because T2b removed 49 unrelated CVEs' bogus CVSS severities)
53+
- by confidence: {'medium': 1445, 'low': 454, 'high': 326}
54+
- by severity: {'Unrated': 1332, 'Info': 750, 'High': 63, 'Medium': 57, 'Low': 20, 'Critical': 3}
5855
- by source:
59-
- geth: 438
60-
- erigon: 371
61-
- lodestar: 276
62-
- nimbus: 232
63-
- lighthouse: 178
64-
- reth: 172
65-
- prysm: 116
66-
- nethermind: 108
67-
- besu: 94
68-
- teku: 93
69-
- grandine: 16
56+
- erigon: 425
57+
- geth: 407
58+
- nimbus: 269
59+
- lodestar: 225
60+
- lighthouse: 217
61+
- reth: 183
62+
- nethermind: 130
63+
- teku: 123
64+
- prysm: 113
65+
- besu: 112
66+
- grandine: 19
7067
- consensus-specs: 2
71-
- security_score distribution: {'0.0': 34, '0.3': 4, '0.5': 1253, '0.8': 423, '0.9': 172, '1.0': 210}
68+
- security_score distribution: {'0.0': 220, '0.3': 271, '0.5': 1119, '0.8': 289, '0.9': 159, '1.0': 167}
7269

7370
## Validation checkpoints (issue #89)
7471

7572
- c-kzg-4844 / blst: present (kzg×12, 4844×13, blst×13 in curated)
76-
- Lodestar: 276 · Nimbus: 232 · Prysm: 116 — all present
73+
- Lodestar: 225 · Nimbus: 269 · Prysm: 113 — all present
7774
- `ethereum_specs` source: **0** (spec-divergence crawler returned no matches this run; the 11 clients + consensus-specs are covered)
7875

79-
## Column coverage (n=2,333)
76+
## Column coverage (n=2,225)
8077

8178
| column | coverage | notes |
8279
|---|---:|---|
8380
| `source_url`, `title`, `description`, `attack_path` | 100.0% | attack_path defaults to a best-effort class |
8481
| `label` (assigned, non-`other`) | **93.4%** | deterministic path/keyword + LLM fallback (`gemma4:31b`) reading the diff or, for no-commit rows, the advisory text |
85-
| `root_cause` (assigned) | 86.8% | keyword + classifier reason + LLM |
86-
| `cwe_top25` (from advisory/diff text via LLM) | **24.7%** | read from the link's advisory text / diff even when no fix commit exists — advisories often lack a CWE but the Impact text yields one |
82+
| `root_cause` (assigned) | 86.5% | keyword + classifier reason + LLM |
83+
| `cwe_top25` (general CWE label assigned) | **17.8%** | despite the legacy column name, only 130 rows (5.8% overall) are in MITRE's 2025 Top 25 |
8784
| `fix_commit` / `introduced_in_commit` | **88.0%** | `/commit/` + `/pull/` URLs, GHSA advisory patch-releases, and **inline `#PR` / commit refs parsed from CHANGELOG/release text** (author-linked, high precision) |
88-
| `pre_fix_code` / `post_fix_code` (inline) | **86.3%** | **98.1% of the 1,959 rows that have a resolved commit** — only 38 committed rows lack a diff (huge/edge-case). The remaining ~266 no-code rows have no single fix commit (advisory/NVD/release) — no code exists to inline. |
89-
| `silent_fix_prob` (LLM classifier) | 38.5% | classified rows (C_candidate + plausible gate-dropped) |
90-
| `severity` (bounty-graded, rated) | 6.4% | the EF bug-bounty grades; most fixes are silently patched, unrated |
91-
| `severity_estimated` (bounty-tier) | **30.3%** | a Low/Medium/High/Critical tier via: 60 `bounty-graded` (ground truth) + 83 `upstream-cvss` (dependency CVEs) + **532 `llm-estimated`** (from the client-code fixes; 1,020 more judged `not-eligible` = not remotely network-impacting). `severity_source` flags provenance. See `docs/severity_labeling.md`. |
92-
85+
| `pre_fix_code` / `post_fix_code` (inline) | **86.4%** | **98.2% of the 1,959 rows with a resolved commit**; 36 committed rows lack post-fix code and 266 have no fix commit |
86+
| `silent_fix_prob` (LLM classifier) | 40.3% | 897 classified rows (C_candidate + plausible gate-dropped) |
87+
| `severity` (rated) | 6.4% | 143 rows; provenance is 60 `bounty-graded` and 83 rated `upstream-cvss` |
88+
| `severity_estimated` (bounty-tier) | **30.3%** | 675 Low/Medium/High/Critical estimates; 1,549 `not-eligible`, one unassessed. See `docs/severity_labeling.md`. |

docs/DATA_SNAPSHOT.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Canonical dataset snapshot
2+
3+
This page is the single source of truth for counts quoted in current
4+
documentation. Every value is computed from
5+
`data/ethereum_vulns.parquet`, not copied from an intermediate build log.
6+
7+
- Rows: **2,225**
8+
- Parquet SHA-256:
9+
`09bb9642023e4fa914268d86e723435cf16626d55f480fc248c43ad6af79e0e7`
10+
- Recompute: `uv run python scripts/paper_analysis.py`
11+
- Machine-readable metrics:
12+
[`paper/tables/snapshot_metrics.csv`](paper/tables/snapshot_metrics.csv)
13+
14+
## Canonical public-evidence definition
15+
16+
A record is **advisory-linked** when a case-insensitive, syntactically valid
17+
CVE, GHSA, or RustSec identifier occurs in any of `title`, `description`,
18+
`issue_id`, `contest`, `source_url`, or `evidence`. A record is **rated** when
19+
`severity` is Critical, High, Medium, or Low.
20+
21+
| Public evidence | Records | Share |
22+
|---|---:|---:|
23+
| Advisory ID and rating | 52 | 2.3% |
24+
| Advisory ID only | 120 | 5.4% |
25+
| Rating only | 91 | 4.1% |
26+
| Neither advisory ID nor rating | **1,962** | **88.2%** |
27+
| Total | 2,225 | 100.0% |
28+
29+
Thus, 172 records (7.7%) have a recognized advisory ID and 143 (6.4%) have a
30+
rated severity. Separately, 2,082 (93.6%) are **unrated**. The 93.6% value must
31+
not be described as “no advisory” or “silent”; it measures rating absence only.
32+
In current documentation, the precise phrase for 88.2% is “neither a recognized
33+
advisory ID nor a rated severity.”
34+
35+
The older title/description-only, case-sensitive `CVE-|GHSA-` search finds 109
36+
records (4.9%). It is retained only as a sensitivity/reproducibility result in
37+
the paper audit, never as the headline definition.
38+
39+
## Current distributions
40+
41+
| Measure | Count |
42+
|---|---:|
43+
| A_authoritative | 235 |
44+
| B_corroborated | 1,573 |
45+
| C_candidate | 417 |
46+
| A ∪ B essential slice | 1,808 |
47+
| Confidence: high / medium / low | 326 / 1,445 / 454 |
48+
| Severity: Critical / High / Medium / Low | 3 / 63 / 57 / 20 |
49+
| Severity: Info / Unrated | 750 / 1,332 |
50+
| Fix commit present | 1,959 (88.0%) |
51+
| Post-fix code present | 1,923 (86.4%) |
52+
| Post-fix code among rows with a fix commit | 1,923 / 1,959 (98.2%) |
53+
| Fix commit present but post-fix code absent | 36 |
54+
| CWE assigned (`cwe_top25 != N/A`) | 396 (17.8%) |
55+
| Assigned CWE in MITRE 2025 Top 25 | 130 (5.8% of all rows) |
56+
| `silent_fix_prob` assessed | 897 (40.3%) |
57+
58+
Despite its legacy name, `cwe_top25` stores general CWE labels. Only 130 of its
59+
396 assigned values are members of MITRE's 2025 Top 25.
60+
61+
## Drift policy
62+
63+
`data/manifest.json` distributions must equal counts computed from the Parquet
64+
snapshot. `tests/test_security_dataset.py` enforces that invariant and also
65+
checks the four-way advisory/rating partition above. Historical experiment logs
66+
may contain iteration-local counts, but they must identify themselves as
67+
historical and link here for the current snapshot.

docs/IMPROVEMENT_LOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ wake-ups; this file is the memory). Each iteration: **acquire additional diff
55
evaluate → improve**, repeated until vulnerability coverage saturates
66
(loop-until-dry: N consecutive iterations add no new distinct vulns).
77

8+
> **Historical ledger:** every count below belongs to the named iteration and
9+
> is not a current dataset statistic. For the current 2,225-row snapshot, use
10+
> [`DATA_SNAPSHOT.md`](./DATA_SNAPSHOT.md).
11+
812
## Baseline (before loop) — 2026-07-02
913
- curated rows: **2096**, rated-severity: **173** (8.3%), high-confidence: **382**
1014
- Diagnosis: volume-driven (60k+ commit-grep rows), only a tiny authoritative core

docs/analysis.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ security fixes across the eleven production Ethereum clients.*
66

77
> **Key takeaways.** For anyone auditing Ethereum clients (or other blockchain /
88
> consensus systems), the data says: (1) the **historical fix record — not the
9-
> CVE list — is the more complete map**, since ~94% of fixes carry no CVE or
10-
> advisory; (2) the threat profile is **availability- and consensus-centric**
9+
> CVE list — is the more complete map**, since 88.2% of records carry neither a
10+
> recognized advisory ID nor a rated severity; (2) the threat profile is
11+
> **availability- and consensus-centric**
1112
> (untrusted network input crashing or diverging a node), not the memory-
1213
> corruption profile of generic C/C++ datasets; (3) by the bounty's impact
1314
> definition, **consensus, EVM and crypto code is where an exploitable bug is
@@ -30,23 +31,25 @@ are computed directly over the curated table; fix-size uses the inlined
3031
post-fix hunks. Method and coverage caveats are in
3132
[`limitations.md`](./limitations.md).
3233

33-
## 2. Finding 1 — the silent-fix majority
34+
## 2. Finding 1 — the advisory-light majority
3435

3536
![Figure 1](figures/fig1_silent_prevalence.png)
3637

37-
**Only 4.9% of curated fixes carry a CVE/GHSA identifier and 6.4% carry any
38-
rated severity; ≈93.6% are silent** — no advisory, frequently an uninformative
39-
commit message. This quantifies, for Ethereum clients specifically, the
40-
silent-patching behaviour that VulFixMiner [Zhou et al., ASE'21] and Sawadogo et
41-
al. describe qualitatively.
38+
Under the canonical search across all provenance fields, **172 records (7.7%)
39+
carry a recognized CVE/GHSA/RustSec identifier**, 143 (6.4%) carry a rated
40+
severity, and **1,962 (88.2%) carry neither**. The four mutually exclusive
41+
groups are: ID + rating 52, ID only 120, rating only 91, and neither 1,962.
42+
The operational definition is fixed in
43+
[`DATA_SNAPSHOT.md`](./DATA_SNAPSHOT.md).
4244

4345
*Implication.* CVE-anchored datasets — **CVEfixes** [Bhandari et al., 2021] and
4446
**BigVul** [Fan et al., 2020] — begin from an advisory and walk to the patch, so
45-
by construction they can only observe the ~5% advisory-linked slice. This corpus
47+
by construction they can only observe an advisory-linked slice. In this corpus,
48+
that slice is 7.7% under the canonical identifier definition. This corpus
4649
is built in the opposite direction (mine the silent majority, then corroborate),
4750
making it **complementary to, not a subset of**, CVE-anchored resources. A model
48-
trained solely on CVE-linked fixes never sees the 94% of Ethereum-client fixes
49-
that never received a CVE.
51+
trained solely on advisory-linked fixes does not see the 88.2% with neither a
52+
recognized advisory ID nor a rated severity.
5053

5154
## 3. Finding 2 — an availability-first, protocol-specific threat profile
5255

@@ -171,9 +174,9 @@ Assessed along **Croft et al.**'s [2023] data-quality dimensions:
171174
- **Currentness.** Freshly crawled (2026), including the newest forks
172175
(deneb→fulu/gloas, cancun→osaka), where public datasets typically lag years.
173176

174-
The low bars — `severity` (6.4%) and `silent_fix_prob` (40%) — are structural,
175-
not defects: unrated severity *is* the silent-fix signal (§2), and full-commit
176-
LLM classification was deliberately bounded (§8).
177+
The low bars — `severity` (6.4%) and `silent_fix_prob` (40.3%) — describe
178+
coverage, not ground truth about disclosure. Full-commit LLM classification was
179+
deliberately bounded (§8).
177180

178181
## 9. Implications for use
179182

-1.12 KB
Loading

0 commit comments

Comments
 (0)