bb-methodology
The "3rd eye" for pentesting — reminds agent to understand before assuming, validate before…
Case study - role misconfiguration bug class applied to a yield aggregator protocol. Use as a template for applying all 10 bug classes to a single target.
$ npx -y skills add Gabson0x/bountyforge --skill web3-case-study-role-misconfig --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/web3-case-study-role-misconfigContext preview
The summary Claude sees to decide when to auto-load this skill.
Case study - role misconfiguration bug class applied to a yield aggregator protocol. Use as a template for applying all 10 bug classes to a single target.
name: web3-case-study-role-misconfig description: Case study - role misconfiguration bug class applied to a yield aggregator protocol. Use as a template for applying all 10 bug classes to a single target. Contains: architecture walkthrough, all bug class verdicts, 2 findings (DISTRIBUTOR_ROLE never granted, dust harvest DoS), complete PoC templates, report drafts, validation steps.
Bug Class: Access Control | Severity: Critical/Medium | Payout Range: $10K–$50K
See also: [[Web3 Audit]], [[Web3 Bug Classes]], [[Methodology]], [[Trust Map]], [[A→B Chains]]
---
| Field | Value | |-------|-------| | Protocol Type | Yield aggregator — stablecoin → lending protocol → harvest → DEX → reward token | | Max Bounty | $50K (Critical) | | TVL | Low (fresh program, under $100K) | | Core Contracts | Vault.sol, RewardsDistributor.sol | | Program Age | ~5 days when hunted (fresh = low competition) | | Prior Audits | Firm A (16 findings, all Risk Accepted) + Firm B (18 findings, all Risk Accepted) |
**Scorecard:** Max bounty (+2) + custom math (+1) + recent code (+1) + known prior audits (+1) + public source (+1) + program new (+2) = **8/10 → HUNT**
**Why this scores high:** Fresh program on a live bounty platform + prior audits that accepted all risk = team is aware of issues but hasn't patched them. Hunt for what auditors missed or flagged but accepted.
---
User deposits Stablecoin
↓ deposit(uint256 amount)
Vault.sol stores:
- deposits[user] += amount
- totalDeposited += amount
- depositTimestamp[user] = block.timestamp
↓ safeTransferFrom(user, address(this), amount)
↓ lendingProtocol.supply(stablecoin, amount, address(this), 0)
Interest-bearing token accrues in Vault.sol balance
↓ (periodic) _performHarvest()
aToken balance > totalDeposited + DUST_THRESHOLD
↓ lendingProtocol.withdraw(stablecoin, harvestAmount - 1, address(this))
↓ dex.exactInputSingle(stablecoin → rewardToken)
↓ RewardsDistributor.distribute(rewardToken, amount)
RewardsDistributor tracks:
- cumulativeRewardPerShare updates
- users can call claimFor(user) to collect rewardToken
User withdraws:
↓ withdraw(uint256 amount)
if block.timestamp < depositTimestamp[user] + LOCK_PERIOD:
withdrawFee applies (e.g. 0.5%)
lendingProtocol.withdraw(stablecoin, amount, user)**Key state variables:**
---
All standard: missing events, gas optimizations, reentrancy guards present (CEI followed), centralization risks (owner can pause), single oracle (DEX swap is operational, not security-critical).
Including:
**Pattern:** Firm B flagged "missing check" but didn't verify the role was actually ungranted. This is the gap to exploit.
---
**Finding 1: The `-1` Stranding Pattern**
// In _performHarvest(): harvestAmount = aToken.balanceOf(address(this)) - totalDeposited - 1; // strands 1 wei
The hardcoded `-1` strands 1 wei of stablecoin per harvest permanently. Over thousands of harvests, this accumulates. Severity: LOW/INFORMATIONAL (no user loss, just protocol dust accumulation).
**Finding 2: Dust Harvest DoS** ← VALID MEDIUM
Scenario: Accumulated harvest amount is very tiny (< DEX minimum swap) 1. harvest() calls dex.exactInputSingle(stablecoin → rewardToken) 2. DEX returns 0 (amount too small to produce any output) 3. RewardsDistributor.distribute(0) is called 4. If distribute() reverts on 0 amount → harvest is permanently frozen 5. Users can still withdraw principal but all future yield is lost Verification: Check if distribute(0) reverts. Check DEX minimum swap threshold.
**Finding: DISTRIBUTOR_ROLE Never Granted** ← MAIN FINDING
// RewardsDistributor.sol
bytes32 public constant DISTRIBUTOR_ROLE = keccak256("DISTRIBUTOR_ROLE");
function claimFor(address user) external {
require(hasRole(DISTRIBUTOR_ROLE, msg.sender), "Not distributor");
// ... distribute rewardToken to user
}**Problem:** `DISTRIBUTOR_ROLE` is defined but NEVER granted in the constructor or any initialization function. No address holds this role. `claimFor()` can never succeed — all reward tokens are permanently locked.
**How Firm B missed it:** They flagged "missing check for whether role is set" — but their fix recommendation was "add a require that checks the role exists." They didn't verify that `getRoleMemberCount(DISTRIBUTOR_ROLE) == 0` on the live deployment.
**Severity Assessment:**
**Verification commands:**
# Check if any address has DISTRIBUTOR_ROLE (replace with actual address) cast call <REWARDS_DISTRIBUTOR_ADDR> \ "getRoleMemberCount(bytes32)(uint256)" \ "$(cast keccak 'DISTRIBUTOR_ROLE')" # Expected: 0 = confirmed bug # Alternative: Etherscan → Events → filter "RoleGranted" # If no RoleGranted events with DISTRIBUTOR_ROLE hash = confirmed
##
All-round bug bounty skill for Claude Code — parallelized agents for smart contract audits (EVM, Move, Solana, TRON), web/API security, local tooling orchestration, and submission-ready reports for HackerOne, Bugcrowd, Intigriti & Immunefi.
Repo: Gabson0x/bountyforge
The "3rd eye" for pentesting — reminds agent to understand before assuming, validate before…
Complete bug bounty workflow — recon (subdomain enumeration, asset discovery, fingerprinting,…
Analyze EVM smart contracts for storage-safety vulnerabilities that can cause persistent…
Generate Echidna/Medusa-compatible Solidity fuzz suites from Foundry or Hardhat projects.…
Convert English-language properties in PROPERTIES.md (produced by the Fizz skill) into…