bug-bounty-intelligence-mcp 1.0.5 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/METHODOLOGY.md CHANGED
@@ -1,94 +1,141 @@
1
- # Methodology: acceptance-rate data behind `list_vulnerability_patterns`
2
-
3
- **Last updated: 2026-07-21.**
4
-
5
- ## What this covers
6
-
7
- `list_vulnerability_patterns` returns, per vulnerability class, what fraction of *submitted*
8
- audit-competition findings were judged valid. This document explains exactly how those numbers
9
- are computed, what they don't claim, and how to reproduce them from public data.
10
-
11
- ## Data source
12
-
13
- Sherlock runs public GitHub repos for judging each audit contest
14
- (`sherlock-audit/<contest>-judging`), where security researchers ("Watsons") open one GitHub
15
- issue per submitted finding. 105 of these judging repos were crawled (27,681 raw submitted
16
- findings total, 2024-02 through 2025-07).
17
-
18
- ## The problem: "was this accepted?" is not reliably answerable from GitHub alone
19
-
20
- An early version of this pipeline inferred acceptance from GitHub issue state
21
- (`OPEN` = accepted, `CLOSED` = rejected). That heuristic turned out to be wrong, in both
22
- directions, depending on the contest — sometimes undercounting acceptance by an order of
23
- magnitude, sometimes overcounting it. GitHub issue labels (`Reward`, `Non-Reward`, `Invalid`,
24
- etc.) turned out to be applied inconsistently across contests too — some contests label every
25
- issue, some label almost none.
26
-
27
- ## The fix: use Sherlock's own result structure, and only trust it when it reconciles
28
-
29
- Sherlock's judging repos also contain, at the repo root, a folder per accepted finding
30
- (`001-M/`, `002-H/`, etc. — number + severity), each containing one file per GitHub issue number
31
- that was judged to be that finding (the primary write-up plus any duplicates), and a separate
32
- `invalid/` folder for rejected issue numbers. This is Sherlock's own authoritative accounting,
33
- not an inference.
34
-
35
- We only trust this for a given contest if it **reconciles exactly**:
36
- `accepted_issue_numbers + invalid_issue_numbers === total_submitted_issues`.
37
-
38
- If a contest's folder structure doesn't exist, or doesn't reconcile exactly, that contest is
39
- **excluded entirely** — not estimated, not approximated. Of 105 crawled contests:
40
-
41
- - **10 reconcile exactly** and are used for `list_vulnerability_patterns`: 1,032 findings, 461
42
- accepted.
43
- - **95 do not** — either no accepted/invalid folder structure exists in that judging repo (a
44
- likely explanation: some contests use a different Sherlock judging workflow that doesn't
45
- generate this structure), or the counts don't sum to the known total (a likely explanation for
46
- many of these: the original crawl capped issue retrieval at 500 per contest, so large contests'
47
- true totals exceed what was captured — a fixable crawler limitation, not a Sherlock data
48
- problem, but not yet re-verified).
49
-
50
- ## What the resulting numbers mean
51
-
52
- For each pattern (keyword-matched against finding titles/bodies — e.g. "reentrancy",
53
- "oracle manipulation"), the acceptance rate is: of all submissions whose issue number appears in
54
- *any* accepted-finding folder (including duplicates of the same underlying bug), what fraction
55
- of total tagged submissions that is. This measures **submission-level reliability** — "if
56
- someone writes up and submits a claim of this bug class, how often does it hold up" — not the
57
- number of distinct vulnerabilities found.
58
-
59
- ## What this does not claim
60
-
61
- - Not a claim about real-world exploit frequency — this is pre-deployment audit-competition
62
- data, not realized incidents.
63
- - Not a claim about detection coverage — it describes submitted reports, not silent misses.
64
- - The keyword-based pattern tagging is a simple substring matcher (see `detectPatterns()` in the
65
- crawler scripts), not a semantic classifier — a manual audit found roughly 65% tagging
66
- precision on a sample. Pattern labels are directional, not exact classifications.
67
- - The 10-contest sample is small. Some pattern rows have thin n (e.g. `liquidation`, n=14) —
68
- treat low-n rows as lower-confidence than high-n rows (e.g. `trusted-actor`, n=311).
69
-
70
- ## Reproducing this
71
-
72
- Everything below uses only public data — no private API, no authentication required beyond
73
- default `gh` CLI rate limits.
74
-
75
- ```bash
76
- # 1. List Sherlock judging repos
77
- gh repo list sherlock-audit --limit 1000 --json name | jq -r '.[] | select(.name | endswith("-judging")) | .name'
78
-
79
- # 2. For a given contest, pull its full file tree and look for the NNN-severity / invalid folders
80
- gh api repos/sherlock-audit/<contest>-judging/git/trees/main?recursive=1 \
81
- | jq '[.tree[].path | select(test("^[0-9]{3}-[HM]/|^invalid/"))]'
82
-
83
- # 3. Cross-check reconciliation against the contest's total issue count
84
- gh api "repos/sherlock-audit/<contest>-judging/issues?state=all&per_page=100" | jq 'length'
85
- ```
86
-
87
- A finding's issue number appearing under any `NNN-[HM]/` path = accepted. Under `invalid/` =
88
- rejected. If `accepted + invalid !== total`, do not use that contest's data.
89
-
90
- ## Why we publish this instead of just the bigger, older number
91
-
92
- An earlier draft of this tool advertised acceptance percentages computed from the full,
93
- unverified 27,681-finding corpus. Independent review caught that those specific percentages
94
- were unreliable. We would rather ship a smaller number we can prove than a bigger one we can't.
1
+ # Methodology: acceptance-rate data behind `list_vulnerability_patterns`
2
+
3
+ **Last updated: 2026-11-12.**
4
+
5
+ ## What this covers
6
+
7
+ `list_vulnerability_patterns` returns, per vulnerability class, what fraction of *submitted*
8
+ audit-competition findings were judged valid. This document explains exactly how those numbers
9
+ are computed, what they don't claim, and how to reproduce them from public data.
10
+
11
+ ## Data source
12
+
13
+ Two platforms, two different public data shapes, two different reconciliation rules (same
14
+ zero-tolerance standard on both). Sherlock first, Code4rena below.
15
+
16
+ Sherlock runs public GitHub repos for judging each audit contest
17
+ (`sherlock-audit/<contest>-judging`), where security researchers ("Watsons") open one GitHub
18
+ issue per submitted finding. 218 of these judging repos were crawled; a real issue count could be
19
+ confirmed for 212 of them, 56,657 raw submitted findings total (2022-09 through 2025-02).
20
+
21
+ ## The problem: "was this accepted?" is not reliably answerable from GitHub alone
22
+
23
+ An early version of this pipeline inferred acceptance from GitHub issue state
24
+ (`OPEN` = accepted, `CLOSED` = rejected). That heuristic turned out to be wrong, in both
25
+ directions, depending on the contest — sometimes undercounting acceptance by an order of
26
+ magnitude, sometimes overcounting it. GitHub issue labels (`Reward`, `Non-Reward`, `Invalid`,
27
+ etc.) turned out to be applied inconsistently across contests too — some contests label every
28
+ issue, some label almost none.
29
+
30
+ ## The fix: use Sherlock's own result structure, and only trust it when it reconciles
31
+
32
+ Sherlock's judging repos also contain, at the repo root, a folder per accepted finding
33
+ (`001-M/`, `002-H/`, etc. — number + severity), each containing one file per GitHub issue number
34
+ that was judged to be that finding (the primary write-up plus any duplicates), and a separate
35
+ `invalid/` folder for rejected issue numbers. This is Sherlock's own authoritative accounting,
36
+ not an inference.
37
+
38
+ We only trust this for a given contest if it **reconciles exactly**:
39
+ `accepted_issue_numbers + invalid_issue_numbers === total_submitted_issues`.
40
+
41
+ If a contest's folder structure doesn't exist, or doesn't reconcile exactly, that contest is
42
+ **excluded entirely** — not estimated, not approximated. Of 218 crawled contests:
43
+
44
+ - **35 reconcile exactly** and are used for `list_vulnerability_patterns`: 7,388 findings, 3,694
45
+ accepted.
46
+ - **183 do not** — either no accepted/invalid folder structure exists in that judging repo (a
47
+ likely explanation: some contests use a different Sherlock judging workflow that doesn't
48
+ generate this structure), or the counts don't sum to the real total.
49
+
50
+ **2026-11-12 re-verification.** The original 2026-07-21 run (105 contests, 1,032 reconciled
51
+ findings) turned out to rest on a locally cached issue count that was itself wrong for most
52
+ contests, not only for the handful capped at the documented 500-issue crawl limit. Spot check: the
53
+ cache said `2022-09-knox-judging` had 12 submissions; the real count is 153.
54
+ `2024-02-optimism-2024-judging` showed 4; the real count is 251. The fix was to stop trusting any
55
+ locally cached total and re-derive it fresh from GitHub's search API for every contest, which is
56
+ also how the reconciliation check itself works (see Reproducing this, below). Re-running the exact
57
+ same reconciliation rule this way, with no local cache in the loop, is what produced the 35/7,388/
58
+ 3,694 figures above.
59
+
60
+ ## Code4rena: a different shape, a much smaller yield
61
+
62
+ Code4rena's `code-423n4/<contest>-findings` repos do not have Sherlock's accepted/invalid folder
63
+ convention. Contests from 2021-2023 instead use per-researcher folders
64
+ (`<researcher>/<researcher>-submission.md`) with no accept/reject marker in the file tree at
65
+ all. Contests from roughly late 2024 onward move the judging data off GitHub entirely; the repo
66
+ is reduced to a README pointing sponsors at code4rena.com (verified on
67
+ `2024-12-unruggable-findings`: the repo contains only that README).
68
+
69
+ What the 2023-era contests do have is GitHub issues with labels, including `satisfactory`,
70
+ `unsatisfactory`, and `partial-NN` (a duplicate that earned partial credit). We tested this
71
+ against five real contests spanning 2021 to 2024: the two 2021-2022 contests sampled had zero
72
+ issues carrying any of these labels (a different, unidentified labelling convention was in use),
73
+ while three contests from October 2023 reconciled exactly once `satisfactory`/`partial-NN`
74
+ (accepted), `unsatisfactory`/`withdrawn-by-warden` (rejected), and
75
+ `qa`/`gas optimization`/`analysis`-only issues (excluded, same treatment as Sherlock's
76
+ `low_info`) were counted against the real issue total.
77
+
78
+ Applying the same zero-tolerance rule (accepted + rejected + excluded must equal the real
79
+ total, with zero unexplained residual) to all 372 crawled Code4rena judging repos: **3 reconcile
80
+ exactly** (1,812 findings, 317 accepted), all three from October 2023. The other 369 either have
81
+ no GitHub-issue judging data at all, or have labels that don't account for every issue. This is
82
+ a small, honest addition on top of Sherlock, not padding — most of Code4rena's history is
83
+ excluded for the same reason most of Sherlock's is: it does not reconcile.
84
+
85
+ ## What the resulting numbers mean
86
+
87
+ For each pattern (keyword-matched against finding titles/bodies — e.g. "reentrancy",
88
+ "oracle manipulation"), the acceptance rate is: of all submissions whose issue number appears in
89
+ *any* accepted-finding folder (including duplicates of the same underlying bug), what fraction
90
+ of total tagged submissions that is. This measures **submission-level reliability** — "if
91
+ someone writes up and submits a claim of this bug class, how often does it hold up" — not the
92
+ number of distinct vulnerabilities found.
93
+
94
+ ## What this does not claim
95
+
96
+ - Not a claim about real-world exploit frequency — this is pre-deployment audit-competition
97
+ data, not realized incidents.
98
+ - Not a claim about detection coverage — it describes submitted reports, not silent misses.
99
+ - The keyword-based pattern tagging is a simple substring matcher (see `detectPatterns()` in the
100
+ crawler scripts), not a semantic classifier — a manual audit found roughly 65% tagging
101
+ precision on a sample. Pattern labels are directional, not exact classifications.
102
+ - The 38-contest sample (35 Sherlock, 3 Code4rena) is still small relative to either platform's
103
+ full history. Some pattern rows have thin n (e.g. `idor`, n=35) — treat low-n rows as
104
+ lower-confidence than high-n rows (e.g. `trusted-actor`, n=2,127).
105
+ - Code4rena's three contests are a narrow, single-month slice (all October 2023), not a
106
+ representative sample of Code4rena's history. Treat the combined numbers as Sherlock-dominated
107
+ (7,388 of 9,200 findings), with Code4rena adding a small, real, but not yet broad correction.
108
+
109
+ ## Reproducing this
110
+
111
+ Everything below uses only public data — no private API, no authentication required beyond
112
+ default `gh` CLI rate limits.
113
+
114
+ ```bash
115
+ # 1. List Sherlock judging repos
116
+ gh repo list sherlock-audit --limit 1000 --json name | jq -r '.[] | select(.name | endswith("-judging")) | .name'
117
+
118
+ # 2. For a given contest, pull its full file tree and look for the NNN-severity / invalid folders
119
+ gh api repos/sherlock-audit/<contest>-judging/git/trees/main?recursive=1 \
120
+ | jq '[.tree[].path | select(test("^[0-9]{3}-[HM]/|^invalid/"))]'
121
+
122
+ # 3. Cross-check reconciliation against the contest's REAL total issue count. Do not paginate
123
+ # an issues?state=all list and take its length: that silently undercounts the moment a
124
+ # contest has more issues than one page covers, which is most of them. The search API's
125
+ # own total_count does not have this problem.
126
+ gh api "search/issues?q=repo:sherlock-audit/<contest>-judging+type:issue" --jq .total_count
127
+ ```
128
+
129
+ A finding's issue number appearing under any `NNN-[HM]/` path = accepted (strip any leading
130
+ zeros before comparing to the issue number from the GitHub API, the folder names are zero-padded
131
+ and the API numbers are not). Under `invalid/` = rejected. If `accepted + invalid !== total`, do
132
+ not use that contest's data.
133
+
134
+ ## Why we publish this instead of just the bigger, older number
135
+
136
+ An earlier draft of this tool advertised acceptance percentages computed from the full,
137
+ unverified raw corpus. Independent review caught that those specific percentages were
138
+ unreliable. We would rather ship a smaller number we can prove than a bigger one we can't. That
139
+ is still true today: the raw Sherlock corpus alone is 56,657 findings, the reconciled,
140
+ trustworthy number published here (Sherlock plus the three Code4rena contests that also
141
+ reconcile) is 9,200.