Skip to content

Commit 664bc2e

Browse files
loop iter 4: fix-impact gate signal + saturation checkpoint
- build_security_dataset.py: add title-level fix-impact to the admission GATE (completes the independent-signal set); description-level rejected as it fires on release notes that merely list a "fix crash". Net +1 new admit — title crash fixes already score >=0.5, confirming deterministic recall is saturated. - test_security_dataset.py: gate test now covers the fix-impact signal. - IMPROVEMENT_LOG: loop-until-dry termination met for deterministic signals; next lever is clone-based backport mining (A1) or the deferred LLM pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5338c37 commit 664bc2e

5 files changed

Lines changed: 47 additions & 17 deletions

File tree

collection/IMPROVEMENT_LOG.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,23 @@ fuzzer report, [A1] cherry-pick/backport. Architecture: **multi-signal scoring**
2626
| 2 | [C] T2b NVD substring-match FP filter (+ source fix in crawl_cve.py) | 885 | 1877 | 173 / 3 | **Authoritative tier was 17% garbage** — "geth" matched as substring in gethostbyaddr/GetHost/"Gether Technology"/Linux usb. Dropped 49 unrelated CVEs (glibc/X.Org/Samba/kernel); 6 real client CVEs kept. Precision iteration: A-tier now clean. |
2727
| 3 | [C5'] fix-verb × impact co-occurrence signal | **1367** | 1877 | 173 / 3 | **C5-via-API dead end**: client PRs almost never use formal `Closes #N` (0/116 inline-ref PRs had closingIssuesReferences). Pivoted to the offline reservoir: "fix panic on…"/"prevent race in…" adjacency is a strong independent defect signal. +482 crash/DoS fixes promoted C→B. 907 rows now ≥2 signals (was 376). `enrich_linked_issues.py` kept as a tool (with `--require-inline-ref`). |
2828

29-
**Loop status:** deterministic precision+recall largely worked through. Essential
30-
slice 173 → 1367 (7.9×) across 3 iterations. Remaining levers are heavier:
31-
[A1] cherry-pick/backport (needs local clones), [B4] review-comment mining
32-
(per-PR API), and the deferred LLM STRIDE/CWE classification (biggest recall
33-
lever — would admit the ~16k unrated stealth fixes; user deferred it, "ひとまずskip").
29+
| 4 | fix-impact added to the GATE (title-level, recall) + test coverage | 1367 | 1878 | 173 / 3 | Title-level fix-impact admits only **+1** new row — title crash fixes already score ≥0.5. Description-level would admit 200 but pulls release-note noise (rejected). **Deterministic recall is saturated.** |
30+
31+
**Loop status — deterministic techniques exhausted (loop-until-dry hit).**
32+
Recall was flat across all 4 iterations (corpus already ~complete at 18,475 raw
33+
/ 1,878 curated); the wins were **precision + tiering**: essential slice
34+
173 → 1367 (7.9×), authoritative tier de-garbaged (−49 unrelated CVEs), −1417
35+
CI/docs/dep-bump noise. Two consecutive iterations (3,4) added <10 new corpus
36+
rows → termination condition met for deterministic signals.
37+
38+
**Remaining levers require a technique switch (heavier / user-gated):**
39+
- [A1] cherry-pick/backport via local clones — the one untapped *new-source*
40+
(geth silently backports fixes to release branches). Uncertain yield, ~1 GB
41+
clones. **Next iteration to attempt.**
42+
- [B4] review-comment mining — per-PR API, slow, low expected yield (like C5).
43+
- **LLM STRIDE/CWE classification** — the biggest recall lever by far (admits the
44+
~16k unrated stealth fixes the keyword gate drops), but the user deferred it
45+
("ひとまずskip"). Re-enabling it is the real path to *comprehensive* coverage.
3446

3547
### Next iterations (API-heavy hidden-fix signals — recall expansion)
3648
- **iter 2** [C5] linked-issue / fuzzer-report signal: `gh pr view --json closingIssuesReferences`, score issue body for crash/panic/fuzz + reporter (oss-fuzz/Guido Vranken).

data/ethereum_vulns.parquet

304 Bytes
Binary file not shown.

data/manifest.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"domain": "ethereum",
3-
"n_rows": 1877,
3+
"n_rows": 1878,
44
"schema": [
55
"id",
66
"source_platform",
@@ -31,19 +31,20 @@
3131
"t2_noise_dropped": 1417,
3232
"t2b_nvd_fp_dropped": 49,
3333
"after_t1": 16998,
34-
"security_rows": 1877,
35-
"low_signal_dropped": 15121,
34+
"security_rows": 1878,
35+
"low_signal_dropped": 15120,
3636
"by_confidence": {
3737
"medium": 1544,
38-
"high": 333
38+
"high": 333,
39+
"low": 1
3940
},
4041
"by_authority_tier": {
4142
"B_corroborated": 1134,
42-
"C_candidate": 510,
43+
"C_candidate": 511,
4344
"A_authoritative": 233
4445
},
4546
"by_n_signals": {
46-
"1": 555,
47+
"1": 556,
4748
"2": 907,
4849
"3": 345,
4950
"4": 65,
@@ -58,21 +59,21 @@
5859
"reth": 161,
5960
"prysm": 114,
6061
"nethermind": 104,
61-
"teku": 90,
62+
"teku": 91,
6263
"besu": 89,
6364
"grandine": 16,
6465
"consensus-specs": 2
6566
},
6667
"by_severity": {
67-
"Unrated": 963,
68+
"Unrated": 964,
6869
"Info": 773,
6970
"High": 63,
7071
"Medium": 54,
7172
"Low": 21,
7273
"Critical": 3
7374
},
7475
"by_score": {
75-
"0.0": 34,
76+
"0.0": 35,
7677
"0.3": 4,
7778
"0.5": 1208,
7879
"0.8": 298,

pipeline/build_security_dataset.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,12 +322,19 @@ def build(df: pd.DataFrame) -> tuple[pd.DataFrame, dict]:
322322
]
323323

324324
# GATE — union of independent signals
325-
has_id = (df["title"].fillna("") + " " + df["description"].fillna("")).str.contains(CVE_RE)
325+
gate_blob = df["title"].fillna("") + " " + df["description"].fillna("")
326+
has_id = gate_blob.str.contains(CVE_RE)
326327
has_sev = df["severity"].fillna("").str.lower().isin(RATED_SEV)
327328
has_kw = df["security_score"] >= 0.5
328329
has_stride = ~df["stride"].fillna("Other").isin(["Other"])
329330
has_cwe = ~df["cwe_top25"].fillna("N/A").isin(["N/A"])
330-
df["security_relevant"] = has_id | has_sev | has_kw | has_stride | has_cwe
331+
# Recall expansion: a fix-verb × crash-class impact co-occurrence in the
332+
# TITLE admits real crash/DoS fixes whose only keyword sat in the description
333+
# (score 0.3, below the 0.5 threshold). Title-only — a description-level
334+
# match also fires on release notes that merely *list* a "fix crash". T2
335+
# already dropped dep-bump/CI titles upstream.
336+
has_fiximpact = df["title"].fillna("").str.contains(FIX_IMPACT_RE)
337+
df["security_relevant"] = has_id | has_sev | has_kw | has_stride | has_cwe | has_fiximpact
331338

332339
sec = df[df["security_relevant"]].copy()
333340
sec["confidence"] = sec.apply(confidence_tier, axis=1)

tests/test_security_dataset.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,17 @@ def test_every_row_has_a_security_signal(df):
4848
has_cwe = ~df["cwe_top25"].fillna("N/A").isin(["N/A"])
4949
blob = df["title"].fillna("") + " " + df["description"].fillna("")
5050
has_id = blob.str.contains(r"CVE-\d{4}-\d{4,7}|GHSA-", case=False, regex=True)
51-
assert bool((has_sev | has_kw | has_stride | has_cwe | has_id).all())
51+
# fix-verb × crash-class impact in the title (recall-expansion gate signal)
52+
fiximpact = re.compile(
53+
r"\b(?:fix|fixes|fixed|prevent|avoid|guard|handle|resolve|correct|patch)\w*"
54+
r"\b[^.\n]{0,40}\b(?:crash|panic|segfault|deadlock|hang|freeze|oom"
55+
r"|out.of.memory|overflow|underflow|data race|race condition|reorg"
56+
r"|non.?determin|infinite loop|use.after.free|null (?:pointer|deref))\b"
57+
r"|\b(?:crash|panic|segfault|deadlock|hang|oom|overflow|underflow|reorg"
58+
r"|race condition)\b[^.\n]{0,25}"
59+
r"\b(?:fix|fixed|prevent|avoid|guard against|resolved|patch)\w*\b", re.I)
60+
has_fiximpact = df["title"].fillna("").str.contains(fiximpact)
61+
assert bool((has_sev | has_kw | has_stride | has_cwe | has_id | has_fiximpact).all())
5262

5363

5464
def test_confidence_values(df):

0 commit comments

Comments
 (0)