bug-bounty-intelligence-mcp 1.0.5 → 1.1.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 +109 -94
- package/README.md +266 -201
- package/index.mjs +286 -284
- package/package.json +43 -40
- package/vulnerability-acceptance-rates.json +91 -97
package/BENCHMARK.md
CHANGED
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
# Benchmark: Bug Bounty Intelligence vs Slither
|
|
2
|
-
|
|
3
|
-
**Target protocol:** 3FLabs/grunt — leveraged strategies for on-chain funds
|
|
4
|
-
**Protocol type:** ERC-4626/ERC-7540 vaults + Morpho lending + flash loan requests
|
|
5
|
-
**Scope:** `src/` (218 Solidity files)
|
|
6
|
-
**Slither version:** 0.10.x
|
|
7
|
-
**Date:** 2026-07-20
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## TL;DR
|
|
12
|
-
|
|
13
|
-
| Metric | Slither | Bug Bounty Intelligence |
|
|
14
|
-
|--------|---------|------------------------|
|
|
15
|
-
| "High" findings reported | 27 | 0 |
|
|
16
|
-
| True positives (real bugs) | 0 | 0 |
|
|
17
|
-
| False positives | 27 (100%) | 0 |
|
|
18
|
-
| Out-of-scope library noise | 24 (89%) | 0 |
|
|
19
|
-
| Signals requiring human review | n/a | 17 |
|
|
20
|
-
| Reading time to triage all "Highs" | ~4 hours | ~15 min |
|
|
21
|
-
|
|
22
|
-
**Slither false positive rate on this codebase: 100%.**
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## Test setup
|
|
27
|
-
|
|
28
|
-
We ran Slither on the full repo (including `lib/`), and Bug Bounty Intelligence on `src/` only. The scope for any real audit is `src/` — dependencies in `lib/` are battle-tested third-party code that has its own audit history.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## Slither's 27 "High" findings — breakdown
|
|
33
|
-
|
|
34
|
-
### Group A: 24 findings in `lib/solady/` (out of scope)
|
|
35
|
-
|
|
36
|
-
Slither flagged 24 High-severity issues, all in the `lib/solady/` dependency:
|
|
37
|
-
|
|
38
|
-
- 5x `incorrect-exp` — Solady uses XOR (`^`) for bitwise operations, not exponentiation. Slither confuses the two.
|
|
39
|
-
- 19x `incorrect-shift` — Solady uses `<<` / `>>` inside assembly blocks. Slither's detector fires on these but they are intentional and correct.
|
|
40
|
-
|
|
41
|
-
**Why these are not bugs:**
|
|
42
|
-
|
|
43
|
-
Solady is one of the most audited Solidity libraries in existence, used by hundreds of protocols. Its math implementations deliberately use low-level bit operations for gas efficiency. Slither's `incorrect-exp` and `incorrect-shift` detectors are known to produce false positives on Solady. Every human auditor knows to ignore these. Slither does not.
|
|
44
|
-
|
|
45
|
-
**Example:**
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
incorrect-shift | FixedPointMathLib.lnWad(int256) (lib/solady/src/utils/FixedPointMathLib.sol#269-341)
|
|
49
|
-
contains an incorrect shift operation: ...
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
This is in `lib/solady/`, a third-party library. It is out of scope for any 3FLabs audit.
|
|
53
|
-
|
|
54
|
-
**Update:** while triaging these, we noticed a related pattern on the same `lib/solady` code:
|
|
55
|
-
`divide-before-multiply` fired 38 times on `FixedPointMathLib`'s `fullMulDivUnchecked`, `cbrt`,
|
|
56
|
-
`invMod`, `rpow`, and `cbrtWad`. We initially described all 38 as the same false-positive class
|
|
57
|
-
(divided variable fully reassigned before the multiply) and reported it upstream —
|
|
58
|
-
[crytic/slither#3039](https://github.com/crytic/slither/issues/3039). That was an overclaim on our
|
|
59
|
-
part. A maintainer merged a fix for the specific reassignment pattern we described
|
|
60
|
-
([crytic/slither#3040](https://github.com/crytic/slither/pull/3040), not yet in a PyPI release as
|
|
61
|
-
of 2026-08-01), and re-testing against the merged code ourselves shows it correctly resolves 11 of
|
|
62
|
-
the 38 (down to 27 remaining on this file). The other 27 keep the divided variable in the same
|
|
63
|
-
expression rather than through a clean reassignment, which is a different pattern this fix
|
|
64
|
-
correctly leaves untouched — we haven't yet re-analyzed whether those are genuine false positives
|
|
65
|
-
too or something else. Filed as a lesson: verify a fix against the actual code before describing
|
|
66
|
-
it as resolving the full scope of what you reported.
|
|
67
|
-
|
|
68
|
-
### Group B: 3 findings in `src/` — all false positives
|
|
69
|
-
|
|
70
|
-
Slither flagged 3 uses of `safeTransferFrom(offer.maker, ...)` as "arbitrary from in transferFrom":
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
arbitrary-send-erc20 | Request.consume() uses arbitrary from in transferFrom:
|
|
74
|
-
_asset().safeTransferFrom(offer.maker, address(this), ptAmount)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Why this is not a bug:**
|
|
78
|
-
|
|
79
|
-
`offer.maker` is not arbitrary. The offer is validated via EIP-712 signature before the transfer (`_validateOffer(offer, signature)` is called on line 421). The maker explicitly signed the offer, authorizing exactly this transfer of exactly this amount. Additionally, `consume()` is gated by `onlyOwnerOrRoles(_ROLE_CONSUMER)` — only trusted consumers can call it.
|
|
80
|
-
|
|
81
|
-
This is a correct and standard EIP-712 off-chain order pattern. Slither has no understanding of EIP-712 and cannot reason about signature-based authorization.
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
## What Bug Bounty Intelligence flags instead
|
|
86
|
-
|
|
87
|
-
After filtering to `src/` and applying Al-Mizaan validation gates, we identified **17 signals requiring human review** — none rising to a confirmed finding:
|
|
88
|
-
|
|
89
|
-
| Category | Count | Description |
|
|
90
|
-
|----------|-------|-------------|
|
|
91
|
-
| `unsafe_keyword` | 15 | Explicit `forge-lint: disable-next-line(unsafe-typecast)` annotations — developer acknowledged unsafe casts. Require review for truncation risk. |
|
|
92
|
-
| `delegatecall` | 2 | `MorphoFlashLoanRequest.execute()` delegatecalls to whitelisted scripts. By design — whitelist check present. |
|
|
93
|
-
|
|
94
|
-
**What we do not flag:**
|
|
95
|
-
- Library code in `lib/` (out of scope)
|
|
96
|
-
- EIP-712 signed transfers (intentional pattern, not a vulnerability)
|
|
97
|
-
- Standard Solady bit operations (well-audited library behavior)
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
## Al-Mizaan filtering — why the 17 signals do not become findings
|
|
102
|
-
|
|
103
|
-
The 15 `unsafe_keyword` signals (explicit forge-lint disables) were put through Al-Mizaan's reachability chain:
|
|
104
|
-
|
|
105
|
-
1. **Code** — read the actual casts in `LibTokenController.sol`, `TokenController.sol`, and `PositionManagerLP.sol`
|
|
106
|
-
2. **Reachability** — are the truncated values reachable by an untrusted caller?
|
|
107
|
-
3. **Threat model** — who triggers these paths? In all cases, the entry points are role-gated (owner, ROLE_CONSUMER, ROLE_MANAGER)
|
|
108
|
-
4. **Invariant** — does the truncation break any accounting invariant?
|
|
109
|
-
|
|
110
|
-
Verdict: The typecasts are `uint256 → uint128` in share accounting. The values come from internal calculations that are capped by deposit limits. Overflow requires either extremely large TVL or a role-gated actor. **Trusted actor as sole trigger → ELIMINATE.**
|
|
111
|
-
|
|
112
|
-
The 2 `delegatecall` signals: `MorphoFlashLoanRequest` maintains an explicit `isScriptWhitelisted` mapping. Only the owner can whitelist. **Trusted actor (owner) as sole attacker → ELIMINATE.**
|
|
113
|
-
|
|
114
|
-
**Result: 0 confirmed findings.** This matches the expected difficulty level of a mature protocol with prior audits.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## Why Slither's false positive rate matters
|
|
119
|
-
|
|
120
|
-
A 100% false positive rate on High findings is not an edge case. It happens because:
|
|
121
|
-
|
|
122
|
-
1. **No scope awareness** — Slither scans everything, including dependencies. A real audit scopes to `src/` only.
|
|
123
|
-
2. **No protocol context** — Slither cannot distinguish between an arbitrary `transferFrom` and an EIP-712-authorized one.
|
|
124
|
-
3. **No library knowledge** — Solady's assembly patterns trigger false positives on multiple detectors.
|
|
125
|
-
|
|
126
|
-
For a developer running Slither for the first time, triaging 27 "High" findings (all false positives) takes hours. For a developer using Bug Bounty Intelligence, the 17 signals that remain after scope filtering take 15 minutes to review.
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## When Slither IS useful
|
|
131
|
-
|
|
132
|
-
Slither is best used as a first-pass tool before human review, with explicit exclusions:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
slither src/ --exclude incorrect-exp,incorrect-shift --filter-paths "lib/"
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
After filtering, Slither's Medium and Low findings become more useful starting points.
|
|
139
|
-
|
|
140
|
-
Bug Bounty Intelligence is designed to do this filtering for you, and adds protocol-context reasoning that static analysis cannot.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
## Reproduce this benchmark
|
|
145
|
-
|
|
146
|
-
The 3FLabs/grunt codebase is public at [github.com/3FLabs/grunt](https://github.com/3FLabs/grunt).
|
|
147
|
-
|
|
148
|
-
To reproduce Slither's output:
|
|
149
|
-
```bash
|
|
150
|
-
git clone https://github.com/3FLabs/grunt
|
|
151
|
-
cd grunt
|
|
152
|
-
pip install slither-analyzer
|
|
153
|
-
slither . --json slither-output.json
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
To use Bug Bounty Intelligence:
|
|
157
|
-
```bash
|
|
158
|
-
npx -y bug-bounty-intelligence-mcp@latest
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
Then in your MCP client:
|
|
162
|
-
```
|
|
163
|
-
scan_contract({ repo_url: "https://github.com/3FLabs/grunt", protocol_type: "LENDING" })
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
---
|
|
167
|
-
|
|
168
|
-
## About Bug Bounty Intelligence
|
|
169
|
-
|
|
170
|
-
Pattern-aware from a corpus of
|
|
171
|
-
|
|
172
|
-
**Cost:** $5 USDC on Base (eip155:8453) via x402.
|
|
173
|
-
**Install:** `npx -y bug-bounty-intelligence-mcp@latest`
|
|
174
|
-
**npm:** [npmjs.com/package/bug-bounty-intelligence-mcp](https://www.npmjs.com/package/bug-bounty-intelligence-mcp)
|
|
1
|
+
# Benchmark: Bug Bounty Intelligence vs Slither
|
|
2
|
+
|
|
3
|
+
**Target protocol:** 3FLabs/grunt — leveraged strategies for on-chain funds
|
|
4
|
+
**Protocol type:** ERC-4626/ERC-7540 vaults + Morpho lending + flash loan requests
|
|
5
|
+
**Scope:** `src/` (218 Solidity files)
|
|
6
|
+
**Slither version:** 0.10.x
|
|
7
|
+
**Date:** 2026-07-20
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## TL;DR
|
|
12
|
+
|
|
13
|
+
| Metric | Slither | Bug Bounty Intelligence |
|
|
14
|
+
|--------|---------|------------------------|
|
|
15
|
+
| "High" findings reported | 27 | 0 |
|
|
16
|
+
| True positives (real bugs) | 0 | 0 |
|
|
17
|
+
| False positives | 27 (100%) | 0 |
|
|
18
|
+
| Out-of-scope library noise | 24 (89%) | 0 |
|
|
19
|
+
| Signals requiring human review | n/a | 17 |
|
|
20
|
+
| Reading time to triage all "Highs" | ~4 hours | ~15 min |
|
|
21
|
+
|
|
22
|
+
**Slither false positive rate on this codebase: 100%.**
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Test setup
|
|
27
|
+
|
|
28
|
+
We ran Slither on the full repo (including `lib/`), and Bug Bounty Intelligence on `src/` only. The scope for any real audit is `src/` — dependencies in `lib/` are battle-tested third-party code that has its own audit history.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Slither's 27 "High" findings — breakdown
|
|
33
|
+
|
|
34
|
+
### Group A: 24 findings in `lib/solady/` (out of scope)
|
|
35
|
+
|
|
36
|
+
Slither flagged 24 High-severity issues, all in the `lib/solady/` dependency:
|
|
37
|
+
|
|
38
|
+
- 5x `incorrect-exp` — Solady uses XOR (`^`) for bitwise operations, not exponentiation. Slither confuses the two.
|
|
39
|
+
- 19x `incorrect-shift` — Solady uses `<<` / `>>` inside assembly blocks. Slither's detector fires on these but they are intentional and correct.
|
|
40
|
+
|
|
41
|
+
**Why these are not bugs:**
|
|
42
|
+
|
|
43
|
+
Solady is one of the most audited Solidity libraries in existence, used by hundreds of protocols. Its math implementations deliberately use low-level bit operations for gas efficiency. Slither's `incorrect-exp` and `incorrect-shift` detectors are known to produce false positives on Solady. Every human auditor knows to ignore these. Slither does not.
|
|
44
|
+
|
|
45
|
+
**Example:**
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
incorrect-shift | FixedPointMathLib.lnWad(int256) (lib/solady/src/utils/FixedPointMathLib.sol#269-341)
|
|
49
|
+
contains an incorrect shift operation: ...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
This is in `lib/solady/`, a third-party library. It is out of scope for any 3FLabs audit.
|
|
53
|
+
|
|
54
|
+
**Update:** while triaging these, we noticed a related pattern on the same `lib/solady` code:
|
|
55
|
+
`divide-before-multiply` fired 38 times on `FixedPointMathLib`'s `fullMulDivUnchecked`, `cbrt`,
|
|
56
|
+
`invMod`, `rpow`, and `cbrtWad`. We initially described all 38 as the same false-positive class
|
|
57
|
+
(divided variable fully reassigned before the multiply) and reported it upstream —
|
|
58
|
+
[crytic/slither#3039](https://github.com/crytic/slither/issues/3039). That was an overclaim on our
|
|
59
|
+
part. A maintainer merged a fix for the specific reassignment pattern we described
|
|
60
|
+
([crytic/slither#3040](https://github.com/crytic/slither/pull/3040), not yet in a PyPI release as
|
|
61
|
+
of 2026-08-01), and re-testing against the merged code ourselves shows it correctly resolves 11 of
|
|
62
|
+
the 38 (down to 27 remaining on this file). The other 27 keep the divided variable in the same
|
|
63
|
+
expression rather than through a clean reassignment, which is a different pattern this fix
|
|
64
|
+
correctly leaves untouched — we haven't yet re-analyzed whether those are genuine false positives
|
|
65
|
+
too or something else. Filed as a lesson: verify a fix against the actual code before describing
|
|
66
|
+
it as resolving the full scope of what you reported.
|
|
67
|
+
|
|
68
|
+
### Group B: 3 findings in `src/` — all false positives
|
|
69
|
+
|
|
70
|
+
Slither flagged 3 uses of `safeTransferFrom(offer.maker, ...)` as "arbitrary from in transferFrom":
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
arbitrary-send-erc20 | Request.consume() uses arbitrary from in transferFrom:
|
|
74
|
+
_asset().safeTransferFrom(offer.maker, address(this), ptAmount)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Why this is not a bug:**
|
|
78
|
+
|
|
79
|
+
`offer.maker` is not arbitrary. The offer is validated via EIP-712 signature before the transfer (`_validateOffer(offer, signature)` is called on line 421). The maker explicitly signed the offer, authorizing exactly this transfer of exactly this amount. Additionally, `consume()` is gated by `onlyOwnerOrRoles(_ROLE_CONSUMER)` — only trusted consumers can call it.
|
|
80
|
+
|
|
81
|
+
This is a correct and standard EIP-712 off-chain order pattern. Slither has no understanding of EIP-712 and cannot reason about signature-based authorization.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## What Bug Bounty Intelligence flags instead
|
|
86
|
+
|
|
87
|
+
After filtering to `src/` and applying Al-Mizaan validation gates, we identified **17 signals requiring human review** — none rising to a confirmed finding:
|
|
88
|
+
|
|
89
|
+
| Category | Count | Description |
|
|
90
|
+
|----------|-------|-------------|
|
|
91
|
+
| `unsafe_keyword` | 15 | Explicit `forge-lint: disable-next-line(unsafe-typecast)` annotations — developer acknowledged unsafe casts. Require review for truncation risk. |
|
|
92
|
+
| `delegatecall` | 2 | `MorphoFlashLoanRequest.execute()` delegatecalls to whitelisted scripts. By design — whitelist check present. |
|
|
93
|
+
|
|
94
|
+
**What we do not flag:**
|
|
95
|
+
- Library code in `lib/` (out of scope)
|
|
96
|
+
- EIP-712 signed transfers (intentional pattern, not a vulnerability)
|
|
97
|
+
- Standard Solady bit operations (well-audited library behavior)
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Al-Mizaan filtering — why the 17 signals do not become findings
|
|
102
|
+
|
|
103
|
+
The 15 `unsafe_keyword` signals (explicit forge-lint disables) were put through Al-Mizaan's reachability chain:
|
|
104
|
+
|
|
105
|
+
1. **Code** — read the actual casts in `LibTokenController.sol`, `TokenController.sol`, and `PositionManagerLP.sol`
|
|
106
|
+
2. **Reachability** — are the truncated values reachable by an untrusted caller?
|
|
107
|
+
3. **Threat model** — who triggers these paths? In all cases, the entry points are role-gated (owner, ROLE_CONSUMER, ROLE_MANAGER)
|
|
108
|
+
4. **Invariant** — does the truncation break any accounting invariant?
|
|
109
|
+
|
|
110
|
+
Verdict: The typecasts are `uint256 → uint128` in share accounting. The values come from internal calculations that are capped by deposit limits. Overflow requires either extremely large TVL or a role-gated actor. **Trusted actor as sole trigger → ELIMINATE.**
|
|
111
|
+
|
|
112
|
+
The 2 `delegatecall` signals: `MorphoFlashLoanRequest` maintains an explicit `isScriptWhitelisted` mapping. Only the owner can whitelist. **Trusted actor (owner) as sole attacker → ELIMINATE.**
|
|
113
|
+
|
|
114
|
+
**Result: 0 confirmed findings.** This matches the expected difficulty level of a mature protocol with prior audits.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Why Slither's false positive rate matters
|
|
119
|
+
|
|
120
|
+
A 100% false positive rate on High findings is not an edge case. It happens because:
|
|
121
|
+
|
|
122
|
+
1. **No scope awareness** — Slither scans everything, including dependencies. A real audit scopes to `src/` only.
|
|
123
|
+
2. **No protocol context** — Slither cannot distinguish between an arbitrary `transferFrom` and an EIP-712-authorized one.
|
|
124
|
+
3. **No library knowledge** — Solady's assembly patterns trigger false positives on multiple detectors.
|
|
125
|
+
|
|
126
|
+
For a developer running Slither for the first time, triaging 27 "High" findings (all false positives) takes hours. For a developer using Bug Bounty Intelligence, the 17 signals that remain after scope filtering take 15 minutes to review.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## When Slither IS useful
|
|
131
|
+
|
|
132
|
+
Slither is best used as a first-pass tool before human review, with explicit exclusions:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
slither src/ --exclude incorrect-exp,incorrect-shift --filter-paths "lib/"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
After filtering, Slither's Medium and Low findings become more useful starting points.
|
|
139
|
+
|
|
140
|
+
Bug Bounty Intelligence is designed to do this filtering for you, and adds protocol-context reasoning that static analysis cannot.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Reproduce this benchmark
|
|
145
|
+
|
|
146
|
+
The 3FLabs/grunt codebase is public at [github.com/3FLabs/grunt](https://github.com/3FLabs/grunt).
|
|
147
|
+
|
|
148
|
+
To reproduce Slither's output:
|
|
149
|
+
```bash
|
|
150
|
+
git clone https://github.com/3FLabs/grunt
|
|
151
|
+
cd grunt
|
|
152
|
+
pip install slither-analyzer
|
|
153
|
+
slither . --json slither-output.json
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
To use Bug Bounty Intelligence:
|
|
157
|
+
```bash
|
|
158
|
+
npx -y bug-bounty-intelligence-mcp@latest
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Then in your MCP client:
|
|
162
|
+
```
|
|
163
|
+
scan_contract({ repo_url: "https://github.com/3FLabs/grunt", protocol_type: "LENDING" })
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## About Bug Bounty Intelligence
|
|
169
|
+
|
|
170
|
+
Pattern-aware from a corpus of 56,657 submitted Sherlock findings across 212 measured contests, acceptance-rate numbers (used by `list_vulnerability_patterns`) are limited to the 7,388 findings across 35 contests that could be exact-reconciled against contest outcomes (3,694 accepted), see METHODOLOGY.md. Uses the Al-Mizaan v3 framework, a 7-gate validation chain (Code, Reachability, Threat-Model, Invariant, Protocol-Intent, Impact, Proof), to filter signals into confirmed findings.
|
|
171
|
+
|
|
172
|
+
**Cost:** $5 USDC on Base (eip155:8453) via x402.
|
|
173
|
+
**Install:** `npx -y bug-bounty-intelligence-mcp@latest`
|
|
174
|
+
**npm:** [npmjs.com/package/bug-bounty-intelligence-mcp](https://www.npmjs.com/package/bug-bounty-intelligence-mcp)
|
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Abdellah Ouadoudi
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdellah Ouadoudi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/METHODOLOGY.md
CHANGED
|
@@ -1,94 +1,109 @@
|
|
|
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
|
-
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.
|
|
16
|
-
findings total
|
|
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
|
|
40
|
-
|
|
41
|
-
- **
|
|
42
|
-
accepted.
|
|
43
|
-
- **
|
|
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
|
|
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
|
+
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. 218 of these judging repos were crawled; a real issue count could be
|
|
16
|
+
confirmed for 212 of them, 56,657 raw submitted findings total (2022-09 through 2025-02).
|
|
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 218 crawled contests:
|
|
40
|
+
|
|
41
|
+
- **35 reconcile exactly** and are used for `list_vulnerability_patterns`: 7,388 findings, 3,694
|
|
42
|
+
accepted.
|
|
43
|
+
- **183 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 real total.
|
|
46
|
+
|
|
47
|
+
**2026-11-12 re-verification.** The original 2026-07-21 run (105 contests, 1,032 reconciled
|
|
48
|
+
findings) turned out to rest on a locally cached issue count that was itself wrong for most
|
|
49
|
+
contests, not only for the handful capped at the documented 500-issue crawl limit. Spot check: the
|
|
50
|
+
cache said `2022-09-knox-judging` had 12 submissions; the real count is 153.
|
|
51
|
+
`2024-02-optimism-2024-judging` showed 4; the real count is 251. The fix was to stop trusting any
|
|
52
|
+
locally cached total and re-derive it fresh from GitHub's search API for every contest, which is
|
|
53
|
+
also how the reconciliation check itself works (see Reproducing this, below). Re-running the exact
|
|
54
|
+
same reconciliation rule this way, with no local cache in the loop, is what produced the 35/7,388/
|
|
55
|
+
3,694 figures above.
|
|
56
|
+
|
|
57
|
+
## What the resulting numbers mean
|
|
58
|
+
|
|
59
|
+
For each pattern (keyword-matched against finding titles/bodies — e.g. "reentrancy",
|
|
60
|
+
"oracle manipulation"), the acceptance rate is: of all submissions whose issue number appears in
|
|
61
|
+
*any* accepted-finding folder (including duplicates of the same underlying bug), what fraction
|
|
62
|
+
of total tagged submissions that is. This measures **submission-level reliability** — "if
|
|
63
|
+
someone writes up and submits a claim of this bug class, how often does it hold up" — not the
|
|
64
|
+
number of distinct vulnerabilities found.
|
|
65
|
+
|
|
66
|
+
## What this does not claim
|
|
67
|
+
|
|
68
|
+
- Not a claim about real-world exploit frequency — this is pre-deployment audit-competition
|
|
69
|
+
data, not realized incidents.
|
|
70
|
+
- Not a claim about detection coverage — it describes submitted reports, not silent misses.
|
|
71
|
+
- The keyword-based pattern tagging is a simple substring matcher (see `detectPatterns()` in the
|
|
72
|
+
crawler scripts), not a semantic classifier — a manual audit found roughly 65% tagging
|
|
73
|
+
precision on a sample. Pattern labels are directional, not exact classifications.
|
|
74
|
+
- The 35-contest sample is still small relative to the full Sherlock history. Some pattern rows
|
|
75
|
+
have thin n (e.g. `idor`, n=18) — treat low-n rows as lower-confidence than high-n rows (e.g.
|
|
76
|
+
`trusted-actor`, n=1,814).
|
|
77
|
+
|
|
78
|
+
## Reproducing this
|
|
79
|
+
|
|
80
|
+
Everything below uses only public data — no private API, no authentication required beyond
|
|
81
|
+
default `gh` CLI rate limits.
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# 1. List Sherlock judging repos
|
|
85
|
+
gh repo list sherlock-audit --limit 1000 --json name | jq -r '.[] | select(.name | endswith("-judging")) | .name'
|
|
86
|
+
|
|
87
|
+
# 2. For a given contest, pull its full file tree and look for the NNN-severity / invalid folders
|
|
88
|
+
gh api repos/sherlock-audit/<contest>-judging/git/trees/main?recursive=1 \
|
|
89
|
+
| jq '[.tree[].path | select(test("^[0-9]{3}-[HM]/|^invalid/"))]'
|
|
90
|
+
|
|
91
|
+
# 3. Cross-check reconciliation against the contest's REAL total issue count. Do not paginate
|
|
92
|
+
# an issues?state=all list and take its length: that silently undercounts the moment a
|
|
93
|
+
# contest has more issues than one page covers, which is most of them. The search API's
|
|
94
|
+
# own total_count does not have this problem.
|
|
95
|
+
gh api "search/issues?q=repo:sherlock-audit/<contest>-judging+type:issue" --jq .total_count
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A finding's issue number appearing under any `NNN-[HM]/` path = accepted (strip any leading
|
|
99
|
+
zeros before comparing to the issue number from the GitHub API, the folder names are zero-padded
|
|
100
|
+
and the API numbers are not). Under `invalid/` = rejected. If `accepted + invalid !== total`, do
|
|
101
|
+
not use that contest's data.
|
|
102
|
+
|
|
103
|
+
## Why we publish this instead of just the bigger, older number
|
|
104
|
+
|
|
105
|
+
An earlier draft of this tool advertised acceptance percentages computed from the full,
|
|
106
|
+
unverified raw corpus. Independent review caught that those specific percentages were
|
|
107
|
+
unreliable. We would rather ship a smaller number we can prove than a bigger one we can't. That
|
|
108
|
+
is still true today: the raw corpus is 56,657 findings, the reconciled, trustworthy number
|
|
109
|
+
published here is 7,388.
|