Web3 Smart Contract Audit
awarexone/Agentic-Bug-Hunter
Guides smart contract audits and bounty target selection with ten DeFi bug classes, kill signals, a Foundry PoC template and grep patterns.
Seven-question triage gate, Immunefi report format and dissected paid bounty examples for deciding whether a smart contract finding is worth submitting.
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-report --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .claude/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .claude/skills/web3-triage-report && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .claude/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-reportType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-report --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .agents/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .agents/skills/web3-triage-report && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .agents/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-report --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .cursor/skills/web3-triage-report && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .cursor/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/tradecatlabs/vibe-coding-cn.git --path research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-report --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .gemini/skills/web3-triage-report && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .gemini/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-reportInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .github/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .github/skills/web3-triage-report && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .github/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install tradecatlabs/vibe-coding-cn web3-triage-report --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/tradecatlabs/vibe-coding-cn.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report .opencode/skills/web3-triage-report && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "web3-triage-report" agent skill from https://github.com/tradecatlabs/vibe-coding-cn/tree/develop/research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report into .opencode/skills/web3-triage-report/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "web3-triage-report", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
web3-triage-reportSeven-question triage gate, Immunefi report format and dissected paid bounty examples for deciding whether a smart contract finding is worth submitting.
The first part is a triage system for smart contract bug bounty findings: a seven-question gate asked in order before any report is written, where one wrong answer means stop. The questions in the excerpt check whether an attacker can use the bug now with specific function calls, whether its impact is on the Immunefi program's in-scope impact list, whether the root cause sits in an in-scope deployed contract, whether it needs admin access, whether earlier audits already acknowledged it, and whether the economic attack is viable.
The description adds an Immunefi report format and 20 real paid bounty examples taken apart, but the excerpt is cut off during the sixth question, so the report template and the examples are not covered here. Bugs that live in external dependencies such as Aave, Uniswap or OpenZeppelin are treated as out of scope.
12 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 5b76a8f. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
python3From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
REWARD_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Web3 Triage and Report Examples loads about 7.7k tokens when it runs. Until then it costs about 72 tokens; SKILL.md has 2,926 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from tradecatlabs/vibe-coding-cn at commit 5b76a8f, republished under its MIT licence (© tradecatlabs). 2,926 words, ~7,654 tokens.
.claude/skills/web3-triage-report/SKILL.md (or your agent's skills folder).Ask these IN ORDER before writing a single word of your report. ONE wrong answer = STOP and move on.
Complete this template:
1. Setup: [what I need]
2. Call: [exact function, exact params]
3. Result: [what I have that I didn't have before]
4. Cost: [gas + capital]
5. ROI: [profit / cost ratio]If you cannot complete steps 2 and 3 with specific function calls: KILL IT.
Go to the Immunefi program page. Find "Impacts in Scope." Match your bug to one of these EXACTLY.
Example impact tiers:
If your bug does not match any impact in scope: KILL IT.
Confirm the exact deployed address is in scope on the program page.
If the bug is in Aave, Uniswap, OpenZeppelin, or any external dependency: KILL IT.
"Admin can drain funds" = centralization risk = KILL IT. "Admin can set parameter X which under condition Y creates DoS" = borderline.
Salvage path: can the bug trigger WITHOUT the admin doing anything unusual?
Find the audit reports for the protocol. Search for "Risk Accepted," "Acknowledged," "Won't Fix."
If your bug matches a known finding: KILL IT.
Edge case: if acknowledged finding + NEW code around it creates a new attack path → that is a new bug, not the acknowledged one. Must prove the new path.
Attacker spends: gas + capital
Attacker gains: tokens stolen or protocol damaged
If profit < cost: KILL IT.Example:
If yes: KILL IT.
Score = Impact × Likelihood × Exploitability (each 1–3)
| Impact=1 (info leak) | Impact=2 (partial) | Impact=3 (theft/freeze) | |
|---|---|---|---|
| L=1 E=1 | 1 (Info) | 2 (Low) | 3 (Low) |
| L=2 E=2 | 4 (Medium) | 8 (High) | 12 (High) |
| L=3 E=3 | 9 (High) | 18 (Critical) | 27 (Critical) |
Rule: When borderline, round DOWN. Over-classification destroys credibility.
Before writing your report, fill in this attack scenario:
Protocol: [name]
Target contract: [address + function]
Preconditions: [what state must exist?]
Attack sequence:
1. Attacker calls [exact function] with [exact params]
2. [What happens in the contract]
3. [What state changes]
4. Attacker ends up with: [X more tokens / broken state / DoS]
Total cost: [gas estimate + capital requirement]
Total gain: [$X stolen / $Y TVL frozen]
Viable? [yes/no + reason]If you can't fill in steps 1–4 with specific values, the bug is not ready to submit.
A triager reviewing your report will immediately check:
If your report can't pass this checklist: revise before submitting.
| Condition | Severity drops |
|---|---|
| Requires specific admin configuration | -1 level |
| Impact limited to a small subset of users | -1 level |
| Requires long time window (>24h) to exploit | -1 level |
| Protocol can detect and pause before loss | -1 level |
| Impact is yield loss, not principal loss | -1 level |
| Bug is theoretical with no practical attack | Down to Info |
| Attack costs more than attacker gains | Invalid |
| Bug | Valid? | Reason |
|---|---|---|
| DISTRIBUTOR_ROLE never granted → claimFor() permanently uncallable | Valid (Medium) | Deployment bug, not admin action, real impact on users |
- 1 strands 1 wei per harvest | Valid (Low/Info) | Real, quantified, honest about minor impact |
| Front-run harvest (acknowledged in prior audit) | Invalid | Known issue = instant rejection |
| Admin can change fee to 100% | Invalid | Centralization risk = almost always OOS |
| Harvest DoS via dust (requires admin misconfiguration) | Borderline | Must prove it triggers without unusual admin action |
| ecrecover returns address(0) = anyone can pass | Valid (Critical) | No preconditions, direct theft |
| Contract uses spot price oracle | Valid (High/Critical) | Flash loan manipulation, well-documented impact |
| Missing slippage parameter | Valid (Medium) | MEV sandwich possible, quantifiable loss |
| GraphQL introspection enabled | Invalid | Info disclosure only, no exploitation path |
| Missing HSTS header | Invalid | Always rejected |
Immunefi tracks your submission:triage ratio. High invalid submission rate → your future reports get lower priority.
Target: 70%+ valid submissions.
Better to submit 3 valid bugs than 10 invalid ones. A low-severity honest submission is better for your ratio than an overclaimed invalid one.
## [Exact function in ContractName] — [root cause in 10 words] leads to [quantified impact]
### Example title:
"_performHarvest() in Ern.sol subtracts hardcoded 1 wei causing reward token
permanent lockup across all harvests"
---
## Summary
[2-3 sentences maximum. What is the bug, where is it, what does it enable.]
The `_performHarvest()` function in `Ern.sol` subtracts a hardcoded `1` from
`userRewards` without distributing or accounting for the remainder. This causes
1 wei of reward token to be permanently locked in the contract after every
harvest, and — more critically — causes a revert when Uniswap returns 0 output
for dust-amount swaps, permanently freezing the harvest function.
---
## Vulnerability Details
**Contract:** `Ern.sol`
**Function:** `_performHarvest()`, line 187
**Type:** Arithmetic error / Incomplete path
**Vulnerable Code:**
```solidity
uint256 protocolFee = (rewardReceived * harvestFee) / 10000;
uint256 userRewards = rewardReceived - protocolFee - 1; // ← BUG: hardcoded -1
if (protocolFee > 0) REWARD_TOKEN.safeTransfer(owner(), protocolFee);
if (totalSharesSupply > 0) {
cumulativeRewardPerShare += (userRewards * 1e18) / totalSharesSupply;
}Comparison Evidence:
Function claimYield() at line 120 correctly handles zero-amount cases.
_performHarvest() at line 187 does not account for the stranded 1 wei
remainder, creating a silent fund loss on every harvest.
Root Cause:
The - 1 subtraction creates a permanent accounting gap. The 1 wei:
cumulativeRewardPerShareAttack Path (numbered, each step is a specific function call):
minYieldAmount to minimum (1 * 10^6 = 1 USDC)harvestTimePeriod passes (24 hours by default)canHarvest() returns true (time condition satisfied)harvest(0) with minOut = 0yieldAmount = 1 → Aave withdraws 1 wei USDCexactInputSingle(1 wei USDC → WBTC) returns 0 outputrewardReceived = 0userRewards = 0 - 0 - 1 = type(uint256).max ← ARITHMETIC UNDERFLOWSeverity: High Category: Temporary freezing of funds
Quantified Impact:
Preconditions: [list any setup conditions required]
[Working Foundry test that runs with forge test -vvv] [Must include console.log output showing actual numbers] [Must compile and pass cleanly]
Expected Output:
[PASS] testHarvestDoS()
Logs:
canHarvest: true
yieldAmount: 1
harvest() REVERTS: arithmetic underflow confirmed
All future harvests blocked until owner intervenesOption 1 — Remove the unexplained -1:
// Before:
uint256 userRewards = rewardReceived - protocolFee - 1;
// After:
uint256 userRewards = rewardReceived - protocolFee;Option 2 — Guard against zero rewardReceived:
if (rewardReceived == 0) {
lastHarvest = block.timestamp;
return;
}ContractName.sol line X (deployed at 0x...)
---
### TITLE FORMULA
[ROOT CAUSE] in [function name] allows [WHO] to [IMPACT]
Examples:
- `Missing access control in setPassword() allows anyone to change the stored password`
- `Reentrancy in refund() enables attacker to drain all ETH before state update`
- `Spot price oracle in getPrice() enables flash loan manipulation of exchange rates`
- `_performHarvest() subtracts hardcoded 1 wei causing permanent harvest DoS when Uniswap returns 0`
---
### IMPACT SELECTION GUIDE
Match your finding to one of these Immunefi tiers (program-specific — always verify):
| Impact | Tier | Typical payout range |
|--------|------|---------------------|
| Direct theft of user funds (no limit) | Critical | $50K–$10M |
| Permanent freezing of funds | Critical | $50K–$10M |
| Protocol insolvency | Critical | $50K–$10M |
| Theft of unclaimed yield | High | $10K–$100K |
| Permanent freezing of unclaimed yield | High | $10K–$100K |
| Temporary freezing of funds (>1 hour) | High | $5K–$50K |
| Contract can't operate due to lack of funds | Medium | $2K–$10K |
| Griefing (damage, no profit motive) | Medium | $2K–$10K |
| Contract fails to deliver promised returns | Low | $500–$2K |
---
### PoC REQUIREMENTS
A PoC that wins bounties must:
1. Be a working Foundry test (`forge test -vvv` passes)
2. Fork mainnet at a specific block number
3. Use real deployed contract addresses
4. Show console.log output with actual dollar amounts or token counts
5. Be reproducible by the triager with a single command
A PoC that gets rejected:
- "Test passes" with no meaningful assertion
- Compilation errors
- Wrong mainnet addresses
- No fork — just unit tests with mocks
- No quantification of impact
---
### COMMON REJECTION REASONS
1. **Vague impact:** "Could potentially cause loss of funds" → Always quantify in USD
2. **No comparison evidence:** "This is missing" without showing what sibling function has it
3. **PoC that doesn't run:** Compilation errors, wrong addresses → test before submitting
4. **Wrong severity:** Overclassifying → damages credibility for future reports
5. **Known issue not checked:** Submitting what's already in the audit report → instant reject
6. **No fix provided:** Shows you don't fully understand root cause
7. **Multiple variants of same bug:** Submit ONE report per root cause
8. **Missing preconditions:** Listing an admin action as if it's freely exploitable
---
### WHAT TRIAGERS ACTUALLY WANT TO SEE
From analyzing winning reports across Immunefi competitions:
- **Comparison evidence is the #1 differentiator.** "Function X has guard Y, Function Z doesn't" is more compelling than just saying "Function Z is missing guard Y."
- **Numbers matter more than words.** "$69,300 TVL frozen" > "significant funds at risk."
- **The fix proves understanding.** A 1-line fix that addresses root cause > a 10-line defensive patch.
- **Attack cost matters.** "1 wei USDC + gas" > "substantial capital required."
- **Preconditions must be honest.** Triagers will test the scenario. If you omit that it requires admin action, they will find it and reject.
---
## PART 3: REAL EXAMPLES
20 paid bounty reports dissected — pattern, technique, key insight.
---
### 1. Wormhole — $10M (Uninitialized Proxy)
**Protocol:** Wormhole bridge
**Payout:** $10,000,000
**Bug class:** Upgrade patterns / Access control
**Root cause:** UUPS proxy implementation contract missing `_disableInitializers()` in constructor. Attacker called `initialize()` directly on the implementation, became guardian, upgraded proxy to malicious contract.
**What the hunter did:** Called `implementation()` on the UUPS proxy → got impl address → called `initialize()` directly on impl → became guardian → upgraded proxy.
**Key insight:** `_disableInitializers()` was missing from the impl constructor. Always check the IMPL contract directly, not the proxy.
**Grep to replicate:**
```bash
grep -rn "function initialize\b" contracts/ -A3
grep -rn "_disableInitializers()" contracts/
# If initialize has no protection on implementation → CRITICALBug class: Access control / Logic error
Root cause: split() called _requireOwned(tokenId) which only checks the token exists — not that msg.sender owns it. Any caller could split any token.
What the hunter did: Read split(). Noticed _requireOwned. Looked up what it does. Found it only checks existence, not ownership. Called split(victimTokenId, 1).
Key insight: _requireOwned ≠ _checkAuthorized. Always verify what the access control function ACTUALLY validates, not just what its name implies.
Grep:
grep -rn "_requireOwned\|ownerOf\b" contracts/ -B5 -A5
# Read the implementation: does it check msg.sender == owner? Or just: does owner exist?onlyNewEpoch on poke()Bug class: Missing guard on sibling function
Root cause: vote() and reset() had onlyNewEpoch(tokenId) modifier. poke() did not. Called poke() unlimited times per epoch to drain FLUX tokens.
What the hunter did: Listed all functions in the vote/veNFT system. Saw vote() and reset() had onlyNewEpoch. Checked poke() — it didn't. Called poke unlimited times per epoch.
Key insight: When you see a modifier on 2 out of 3 sibling functions, the 3rd is the bug. This is the "missing guard on sibling function" pattern — one of the most common Critical findings.
Grep:
grep -rn "function vote\|function poke\|function reset" contracts/ -A2
# Compare: do all have the same set of modifiers?startUnstake() Phantom RewardsBug class: Accounting desync
Root cause: startUnstake() decremented totalSupply but balanceOf(this) didn't change. harvest() computed yieldAmount = balanceOf(this) - totalSupply — now larger than real. Phantom yield created from the desync.
What the hunter did: Traced the flow. startUnstake() decrements totalSupply. harvest() computes yield from the difference. After startUnstake, the difference is now artificially inflated.
Key insight: Always trace what balanceOf(this) - totalSupply equals in each state transition.
Grep:
grep -rn "balanceOf(address(this)).*-.*total\|total.*-.*balanceOf(address(this))" contracts/
# Then: does totalSupply change without balanceOf changing?Bug class: Missing validation before array push
Root cause: increaseCollateral() called colPools.push(pool) BEFORE checking amount > 0. Depositing 0 pushed the pool entry. Call 5 times → 5 entries for same pool → getLoanLiquidity counted it 5×, inflating collateral.
What the hunter did: Read increaseCollateral(). Found colPools.push(pool) before the amount check. Deposited 0 five times to inflate collateral count.
Key insight: Check that array push operations have amount validation BEFORE the push — not after.
Grep:
grep -rn "\.push(" contracts/ -B5
# Is there: require(amount > 0) or if (amount == 0) return; BEFORE the push?> vs >= Infinite DrainBug class: Off-by-one / boundary condition
Root cause: if (endPeriod > nextClaimablePeriod) — the equal case (==) fell through to the active rewards branch instead of the completed branch. After exiting, attacker could claim infinite rewards.
What the hunter did: Read the delegation period claiming function. Found > comparison. Asked "what when equal?" Traced: equal case triggers infinite reward claim loop.
Key insight: At EVERY > comparison, ask "what when equal?" Boundary conditions are the most common source of off-by-one criticals.
Grep:
grep -rn "endPeriod\|exitPeriod\|lastPeriod" contracts/ | grep "[<>][^=]"
# For every strict comparison on period/epoch boundaries: test the equal caseBug class: Logic error / Tautology
Root cause: require(sourceAddressesRoot == sourceAddressesRoot) — comparing a variable to itself. Always true. Non-payment proof verification was bypassed entirely.
What the hunter did: Read the non-payment proof validation. Found the tautological comparison. Recognized it as always-true.
Key insight: Run a tautology check on every codebase. Any variable compared against itself is a critical bypass.
Grep:
grep -rn "require\|assert" contracts/ | python3 -c "
import sys, re
for l in sys.stdin:
if re.search(r'\b(\w{4,})\b.*==.*\b\1\b', l):
print(l.strip())
"Bug class: Non-standard EVM behavior
Root cause: On Aurora (custom EVM chain), delegatecall to precompile addresses caused balance deduction in the precompile's context — not the caller's. Attacker delegatecalled the precompile: ETH sent to NEAR, but caller's balance unchanged.
What the hunter did: On a custom EVM chain, tested delegatecall to precompile addresses. Found that balance accounting diverged from mainnet Ethereum behavior.
Key insight: On L2s and custom EVMs, test every opcode's behavior — it may differ from mainnet Ethereum. This is especially true for precompiles, gas mechanics, and storage.
Replicable on: Any chain with custom precompiles, EVM-Cosmos hybrids, Substrate-based EVMs.
Bug class: Cryptography / Missing validation
Root cause: transferWithSig() did not check if from had sufficient balance. If ecrecover returned address(0) (invalid signature), from = address(0) and tokens were minted from the void.
What the hunter did: Read transferWithSig(). Checked: is from balance validated? No. Checked: if ecrecover returns address(0), does it revert? No. Called with invalid signature → minted from zero address.
Key insight: Always test ecrecover with an invalid signature. The return value must be compared against address(0) and must revert.
Grep:
grep -rn "ecrecover\|ECDSA\.recover" contracts/ -A5
# Is the return value compared against address(0)?
# Is the from-balance verified before transfer?Bug class: Configuration / Logic error
Root cause: Cosmos SDK documentation specifies that BlockedAddrs map must include all module accounts. The module account was not in BlockedAddrs. Sending tokens to it corrupted chain state and caused a halt.
What the hunter did: Read the Cosmos SDK documentation. Found the requirement. Read app.go. Found the module account was missing from BlockedAddrs. Demonstrated the chain halt.
Key insight: READ THE DOCS. The documentation describes the correct behavior. If the code doesn't match, that's the bug. This is especially true for framework-specific requirements (Cosmos SDK, Anchor, Move runtime).
Bug class: Token standards / Deprecated pattern
Root cause: DEX wrapper used safeApprove(router, amountIn) before a partial swap with no cleanup. Second swap failed with "approve from non-zero to non-zero." First amount locked in wrapper permanently.
What the hunter did: Read the DEX wrapper. Found safeApprove. Asked: "What happens to leftover approval?" No cleanup. Traced: second call reverts, amount locked.
Key insight: safeApprove is deprecated for a reason — it fails if there's a non-zero existing approval. Must call safeApprove(spender, 0) after every swap.
Grep:
grep -rn "safeApprove\b" contracts/ -A8
# Is there: safeApprove(spender, 0) cleanup after the swap?remaining_accounts Validation ($505K)Bug class: Solana-specific / Missing account validation
Root cause: increase_liquidity.rs used remaining_accounts[0] as TickArrayBitmapExtension with no validation that it was actually that account. Passing arbitrary account flipped tick bitmap, draining the pool.
What the hunter did: On Solana, read increase_liquidity.rs. Found remaining_accounts[0] used without validation. Passed arbitrary account to flip tick bitmap.
Key insight: On Solana, every remaining_accounts[n] must be validated with ownership check, discriminator check, or constraint attribute.
Grep (Solana):
grep -rn "remaining_accounts\[" src/ --include="*.rs" -A5
# Is there: AccountInfo constraint, owner check, discriminator check?Bug class: Cryptography / Missing deduplication
Root cause: validSignatures++ incremented without deduplication. One malicious validator stuffed its own signature 100× → reached 66% threshold alone, bypassing consensus.
What the hunter did: Read the consensus code. Found validSignatures++ with no deduplication check. Demonstrated one validator reaching threshold by repeating its signature.
Key insight: Any counter that increases per-signature must check for duplicate signers. if (seen.has(signer)) continue; seen.add(signer) is the fix pattern.
Grep:
grep -rn "validSignatures++\|signatureCount++" src/ -B5
# Is there: if (seen.has(signer)) continue; seen.add(signer)?Bug class: Arithmetic / Precision loss
Root cause: Protocol supported EURS token (2 decimals). Deposit of 1 wei EURS: viewRawAmount(1) → division result rounds to 0 → deposit 0 underlying, receive non-zero LP shares.
What the hunter did: Noted the protocol supported EURS (2 decimals). Ran the calculation manually: 1 wei → 0 underlying after rounding → free LP shares.
Key insight: For any non-standard decimal token (2, 4, 6 decimals), test edge cases near 0. Division truncates toward zero, which creates deposit-with-0-cost attacks.
Grep:
grep -rn "\.decimals()\|EURS\|2.*decimal\|decimals.*2\b" contracts/
# For any non-standard decimal token: test edge cases near 0Bug class: Incomplete path / Fast path skip
Root cause: claimRedemption() had a fast path: if Transmuter already had funds, it transferred and burned NFT, then returned early. The slow path updated cumulativeEarmarked, _redemptionWeight, totalDebt. Fast path skipped all 3. Phantom debt + stranded collateral.
What the hunter did: Read claimRedemption(). Found if (transmuter has funds) { transfer; burn; return; }. Traced which state updates the slow path does that the fast path skips.
Key insight: For every early return or fast path: which state updates happen in normal flow but NOT here? The delta is the bug. This is the most common Critical pattern across Immunefi competition reports.
Grep:
grep -rn "if.*sufficient\|fast.*path\|return\b" contracts/ -B3 -A10
# For each early return: which state updates happen in normal flow but NOT here?Bug class: Oracle manipulation / Flash loan
Root cause: Vault's price per full share calculation included a temporary value before it was settled. Flash loan inflated the transient value → manipulated ppfs → borrow against inflated collateral.
What the hunter did: Read the vault's pricePerFullShare calculation. Found it included a value that could be temporarily inflated. Flash loaned to inflate it, then borrowed against the inflated collateral.
Key insight: Any vault with pricePerFullShare or exchangeRate that is readable AND writable in the same transaction is a flash loan oracle target. Check: can a single transaction inflate then read the rate?
Bug class: Arithmetic / First depositor inflation
Root cause: convertToShares had no virtual offset. First depositor could inflate exchange rate: deposit 1 wei → donate 999,999 → next depositor gets 0 shares for 999,999 deposit.
What the hunter did: Read convertToShares. No totalSupply() + 10**decimalsOffset() virtual offset. Executed the classic first depositor inflation: deposit 1 → donate → victim gets 0.
Key insight: No virtual offset in ERC4626 = first depositor inflation possible. Fix: OpenZeppelin's implementation adds totalSupply() + 10**_decimalsOffset() to numerator.
Grep:
grep -rn "convertToShares\|_convertToShares" contracts/ -A5
# Is there: totalSupply() + 10**decimalsOffset() or totalAssets() + 1?Bug class: Type system / Non-EVM chain specifics
Root cause: On Starknet (Cairo), account_transfer_partial() took felt252 input (which can be negative). Validation amount >= min_amount passed even for negative values. balance - (-X) = balance + X → infinite balance.
What the hunter did: On Starknet, read account_transfer_partial(). Input type is felt252. Tested: does validation pass for negative felt252? Yes. balance - (-X) = balance + X.
Key insight: On Cairo/Starknet, felt252 can represent negative values. Any amount validation must check amount > 0 explicitly, not just amount >= minimum.
Bug class: Arithmetic / Type truncation
Root cause: uint16((bigValue * 10_000) / collateral) — result cast to uint16 without range check. Value 65,537 truncates to 1. Collateral factor validation passed. Deposited $0.003, issued $10,000 LOC.
What the hunter did: Found uint16(...) cast in collateral factor calculation. Calculated: what input produces a value just above 65,535? That value truncates to 1. Passed that input.
Key insight: Any downcast (uint256 → uint16, uint128 → uint64) without a range check is a critical candidate if the result is used for security validation.
Grep:
grep -rn "uint16(\|uint8(\|as u16\|as u8" contracts/ src/
# Is there a range check before the downcast?Bug class: Arithmetic / Exchange rate manipulation
Root cause: Protocol used virtual shares but with too-small virtual offset. Near-empty vault (1 existing share) still allowed exchange rate manipulation when total assets were very low.
What the hunter did: Found that virtual shares were present but the offset was small enough that with 1 existing share and very low TVL, the same inflation math still applied.
Key insight: Even WITH virtual shares, very low TVL vaults can still be vulnerable if the virtual offset is too small relative to the token's price. Test at TVL = 1 share.
Almost every bug comes from one of three root causes:
Pattern A: "I assumed function B was called, but it wasn't"
Pattern B: "I assumed the check meant X, but it actually means Y"
_requireOwned = existence not ownership> = doesn't include boundaryPattern C: "I assumed this can't happen, but it can"
→ NEXT: 06-methodology-research.md
© tradecatlabs, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report of tradecatlabs/vibe-coding-cn.
Open the folder on GitHubat commit 5b76a8f
We found 2 copies of this SKILL.md (exact, near-identical or edited) in other folders, from 2 other GitHub owners. This page covers the copy in tradecatlabs/vibe-coding-cn, which our catalogue first saw on October 7, 2026.
Web3 Triage and Report Examples next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Web3 Triage and Report Examples this skilltradecatlabs/vibe-coding-cn | 17k | 2 repos | ~7.7k | Automated safety check: Pass | MIT | |
| Web3 Smart Contract Auditawarexone/Agentic-Bug-Hunter | 5.3k | 3 repos | ~4.5k | Automated safety check: Pass | MIT | |
| Bug Bounty Report Writingawarexone/Agentic-Bug-Hunter | 5.3k | 2 repos | ~3.9k | Automated safety check: Pass | MIT | |
| Write Reportbugbountywithmarco/bugbounty-disclosed-reports | 120 | — | ~585 | Automated safety check: Pass | None | |
| Report Writingsickn33/agentic-awesome-skills | 47k | 1 repos | ~3.2k | Automated safety check: Pass | MIT | |
| Bugcrowd Reportingsickn33/agentic-awesome-skills | 47k | 1 repos | ~5.9k | Automated safety check: Pass | MIT |
awarexone/Agentic-Bug-Hunter
Guides smart contract audits and bounty target selection with ten DeFi bug classes, kill signals, a Foundry PoC template and grep patterns.
awarexone/Agentic-Bug-Hunter
Guides writing bug bounty reports for HackerOne, Bugcrowd, Intigriti and Immunefi: impact-first titles, proven claims, CVSS 3.1 scoring and a pre-submit checklist.
bugbountywithmarco/bugbounty-disclosed-reports
Write a disclosure-quality bug bounty report for a finding, matching the HackerOne report format used in this repo's corpus.
sickn33/agentic-awesome-skills
Bug bounty report writing for H1/Bugcrowd/Intigriti/Immunefi
sickn33/agentic-awesome-skills
Bugcrowd-specific reporting tactics complementing report-writing
elementalsouls/Claude-BugHunter
Bug bounty report writing for H1/Bugcrowd/Intigriti/Immunefi — report templates, human tone guidelines, impact-first writing, CVSS 3.1 scoring, title formula, impact statement formula, severity…
tradecatlabs/vibe-coding-cn
Meta-skill that turns docs, APIs, code or specs into a reusable skill with references and a quality gate, and refactors skills that are unclear or misfire.
tradecatlabs/vibe-coding-cn
A master set of ten grep command blocks that surface likely vulnerability classes in Solidity source within the first 30 minutes of auditing a new protocol.
tradecatlabs/vibe-coding-cn
Operates tmux sessions like an administrator: reads pane output, sends keys, inspects many panes at once, and coordinates multiple AI terminals through a swarm state script, built on oh-my-tmux.
tradecatlabs/vibe-coding-cn
A selection guide to AI-driven tools for Web3 bug bounty work, from autonomous web pentesters to smart contract bug finders, with notes on authorization.
tradecatlabs/vibe-coding-cn
Runs Slither and Mythril against Solidity contracts to find reentrancy, overflow and access-control bugs before mainnet deployment, then triages and reports findings.
tradecatlabs/vibe-coding-cn
Runs reproducible math computations and counterexample searches with SymPy, NumPy and mpmath, logging evidence without presenting results as proofs.
Categories
Seven-question triage gate, Immunefi report format and dissected paid bounty examples for deciding whether a smart contract finding is worth submitting. The first part is a triage system for smart contract bug bounty findings: a seven-question gate asked in order before any report is written, where one wrong answer means stop. The questions in the excerpt check whether an attacker can use the bug now with specific function calls, whether its impact is on the Immunefi program's in-scope impact list, whether the root cause sits in an in-scope deployed contract, whether it needs admin access, whether earlier audits already acknowledged it, and whether the economic attack is viable.
Web3 Triage and Report Examples fits situations like: deciding whether a smart contract finding is worth submitting; matching a bug to an Immunefi program's in-scope impacts; checking prior audit reports for an already acknowledged issue; writing an Immunefi report after the finding has passed triage.
Run `npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a claude-code`. Or copy the skill folder (research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report in tradecatlabs/vibe-coding-cn) into .claude/skills/web3-triage-report in your project. Claude Code loads it when a task matches its description.
Run `npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a codex`. Or copy the skill folder (research/vibe-cybersecurity-cn/skills/web3-bug-bounty-hunting/web3-triage-report in tradecatlabs/vibe-coding-cn) into .agents/skills/web3-triage-report in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add tradecatlabs/vibe-coding-cn --skill web3-triage-report -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/web3-triage-report, .gemini/skills/web3-triage-report, .github/skills/web3-triage-report and .opencode/skills/web3-triage-report in your project.
Going by SKILL.md and its folder, Web3 Triage and Report Examples needs the command-line tools its instructions call (python3) and credentials named REWARD_TOKEN.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Web3 Triage and Report Examples is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7.7k tokens (SKILL.md is roughly 31k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Web3 Triage and Report Examples: Web3 Smart Contract Audit (awarexone/Agentic-Bug-Hunter, 5.3k stars), Bug Bounty Report Writing (awarexone/Agentic-Bug-Hunter, 5.3k stars), Write Report (bugbountywithmarco/bugbounty-disclosed-reports, 120 stars) and Report Writing (sickn33/agentic-awesome-skills, 47k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
tradecatlabs (a GitHub user) maintains it in tradecatlabs/vibe-coding-cn, which has 17,386 GitHub stars. The repository holds 17 skills in this directory. The repository was last updated on October 10, 2026.
Source: tradecatlabs/vibe-coding-cn on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.