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/BENCHMARK.md +174 -174
- package/LICENSE +21 -21
- package/METHODOLOGY.md +141 -94
- package/README.md +266 -201
- package/index.mjs +289 -284
- package/package.json +43 -40
- package/vulnerability-acceptance-rates.json +92 -97
package/METHODOLOGY.md
CHANGED
|
@@ -1,94 +1,141 @@
|
|
|
1
|
-
# Methodology: acceptance-rate data behind `list_vulnerability_patterns`
|
|
2
|
-
|
|
3
|
-
**Last updated: 2026-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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.
|