From bnb-bounty-reviewer
Use when reviewing BNB Chain bug bounty submissions or security findings against in-scope BNB repositories such as bsc, bsc-genesis-contract, opbnb, op-geth, greenfield, greenfield-cosmos-sdk, greenfield-contracts, greenfield-storage-provider, or tss-lib. Trigger on reports involving BSC/Parlia, Greenfield/SP, cross-chain packages, system contracts, validator logic, RPC/P2P, threshold-signing/TSS, mnemonics/keys, or exploit PoCs.
npx claudepluginhub bnb-chain/bnb-bounty-reviewer --plugin bnb-bounty-reviewerThis skill uses the workspace's default tool permissions.
Review BNB Chain bug bounty reports by verifying the actual code, rejecting fabricated or out-of-scope claims, and mapping valid issues to the correct bounty severity.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Review BNB Chain bug bounty reports by verifying the actual code, rejecting fabricated or out-of-scope claims, and mapping valid issues to the correct bounty severity.
Match the report's target to the right local repo before reading code.
BNB Smart Chain / BNB Beacon Chain
./bsc — BSC Go node (Parlia consensus, EVM, p2p, RPC)./bsc-genesis-contract — system contracts (StakeHub, Governance, SlashIndicator, cross-chain)BNB Greenfield
./greenfield — Greenfield chain client / SDK code./greenfield-cosmos-sdk — Cosmos SDK fork used by Greenfield./greenfield-contracts — Greenfield bridge and resource-mirror contracts./greenfield-storage-provider — Storage Provider services and gRPC handlersopBNB
./opbnb — opBNB rollup node./op-geth — op-geth execution client for opBNBThreshold Signing
./tss-lib — threshold-signing library used by BNB Chain systemsAll paths are relative to the repo root where Claude/Codex is running. If a repo is missing locally, say so and lower confidence rather than hallucinating.
Always read the actual source. Never trust reporter snippets, quoted line numbers, or PoC claims without checking the live code path yourself. When a report quotes a specific constant, sentinel value, or magic ID, verify the exact bytes/hex in source — reporters frequently get these wrong, which makes their PoC non-functional even if the underlying mechanism exists.
Greenfield oracle module: For reports in x/oracle/keeper/, trace the full call chain. Claim -> CheckClaim -> IsRelayerValid -> getInturnRelayer has early exits. Distinguish consensus-critical transaction paths from gRPC query handlers with panic recovery.
Sometimes-mentioned repo: bsc-relayer shows up in submissions. Verify current bounty scope first. Architecturally it relays messages between the Cosmos version of BNB Chain and BSC; claims that it can arbitrarily "mint tokens" are a common fabrication pattern.
Out of scope — reject immediately:
Check current program scope before deep review. If the report targets a repo or service outside the current bounty program, reject on scope. If the same submission is also architecturally impossible, say both.
Latent / governance-gated issues: If the bug only triggers after a privileged governance/admin parameter changes from a currently safe value, default to REJECTED unless:
Required fields: Chain, attack scenario, impact, affected components, and reproduction steps. Missing suggested fix is fine. Missing fields do not automatically kill the report if enough technical detail exists to verify it.
Fabrication / AI-generated report clues:
Reachability matters: For admin, HTTP, WebSocket, gRPC, signer, or Storage Provider findings, a bug in an internal handler is not enough. Verify whether the route is actually public, whether auth is enforced server-side, and whether the deployed listener topology exposes it.
Admin-RPC-gated findings: BSC's admin namespace is available via local IPC by default but is NOT included in default HTTP modules (["net", "web3"]) or WS modules. Reports that assume admin_* methods are remotely accessible are describing operator misconfiguration, not a BSC vulnerability. If an attacker already has admin RPC access, they have far stronger primitives available (admin_addPeer, admin_addTrustedPeer, admin_startHTTP, chain import/export) — evaluate whether the reported "vulnerability" adds any capability beyond what admin access already grants.
Read the referenced files and enough surrounding context to understand:
Always verify preconditions are reachable. Common false positives:
GnfdLightClient.consensusStateBytes) and only the system contract can update it, an attacker calling the precompile directly with crafted data only affects their own context — not the bridge's state. The code defect may still be real (INFORMATIONAL) but the exploit path through the deployed bridge is not.Attack timing: If the report says the attacker must "wait X days" or "only after rotation", confirm the relevant expiration/timelock is actually consulted on the auth path.
Fix safety: If the suggested fix deletes mappings or state, check whether other live paths still depend on that data.
Do not accept the reporter's characterization of data structures. When a report calls something a "ban list" or "security mechanism," trace what actually writes to it and what reads from it. For example, disconnectEnodeSet in BSC's P2P server is not an automatic malicious-peer ban list — entries are only added when the local node itself issues DiscRequested during RemovePeer. It is an operator-managed reconnect suppression set. Mislabeling a data structure inflates perceived impact. Always describe what the code does, not what the reporter says it does.
Verify PoC constants match source. If a PoC uses a specific magic value, enode ID, address, or sentinel, check the exact value in source. A PoC that uses the wrong constant (e.g., all-0xFF when the actual sentinel is a different hash) is non-functional regardless of whether the underlying mechanism exists.
Related-pattern claims: Similar-looking stale mappings or helper functions are only the same vulnerability if there is a live caller that uses them today.
Backport / upstream-fix reports: A cited upstream patch is not enough. Verify the vulnerable code is still present in BNB's branch/tag and that the upstream exploit path really applies to this fork's reachable code.
Validator-set math: Reports often confuse maxElectedValidators with the actual consensus validator set:
snap.Validators comes from getCurrentValidators() / getMiningValidators()numOfCabinets controls the mining set used by Parlia consensusmaxElectedValidators is not automatically the same thing as the active consensus setFork-dependent parameters: Do not use genesis defaults for current-state analysis.
TurnLength changed from 1 to 8 and then to 16 at Maxwell on June 30, 2025numOfCabinets = 21 and maxElectedValidators = 45 unless the code proves otherwiseGovernance-control rhetoric: Do not claim "a colluding majority already controls the network" unless you actually verified quorum and threshold parameters.
Cross-chain package typing and atomicity:
Cross-chain sequence fields: In Greenfield, first ask whether downstream apps actually use appCtx.Sequence for replay protection, ordering, or state changes. If it is only emitted in events and replay protection already happens upstream, the bug is informational or rejected.
Verifier return values: If a contract or service treats "non-empty bytes" as success, check whether it decodes the actual boolean result. Then ask whether the attacker can reach that failure mode in the deployed verifier, rather than only in a patched/mock environment.
Public signer / key-exposure paths:
TSS / cryptographic findings: Separate these classes carefully:
Do not accept P* / P1 key-theft claims unless the PoC actually demonstrates key recovery or signature forgery under the library's real equations and checks. A session-binding defect without direct secret extraction is usually lower severity.
System transaction nonce handling: In BSC, a block with a failed system transaction is invalid and rejected. That is not an irreparable consensus split by itself.
EVM state snapshot revert on error. When a report claims "partial state changes persist after out-of-gas," check the outer call context. evm.Call / StaticCall / DelegateCall take a state snapshot before execution (evm.go:243) and revert on any error (evm.go:314). An error return from Run() (including ErrOutOfGas) causes the entire call's state changes to be rolled back. "Partial state persists" is only true if Run() returns nil error (clean exit), not if it returns an error.
Opcode optimizer fallback control flow. For super-instruction reports: when the fused constantGas equals the sum of individual sub-opcode costs (which is true for all current super-instructions in jump_table.go), the fallback executing sub-opcodes sequentially will always hit the same gas boundary and return an error — the successful-nil-return path is unreachable. However, the return nil, err at interpreter.go:274/315 instead of continue is a real control-flow defect (the comment at interpreter_si.go:109 says "shall continue in main loop"). Use REJECTED (inaccurate as submitted) — not flat REJECTED — because the code bug exists but the reporter's trigger doesn't work on current code. Tell the reporter what they need to prove: a concrete super-instruction where fallback returns nil.
Before accepting a report, compare it against known intended behaviors:
Epoch snapshots are consensus authority. Parlia snapshots are not a stale cache. Mid-epoch StakeHub changes take effect at the next epoch boundary by design.
System transaction ordering is intentional. In BSC Finalize(), common transactions execute before system transactions. Reports framing this as a race usually describe intended protocol behavior.
Transfer gas limits are a deliberate trade-off, but compatibility gaps are real. transferGasLimit in StakeCredit/StakeHub (default 5000, governance range 2300-10000) is intentional anti-griefing. However, do not flat-REJECT reports about contract wallets being unable to claim. The key questions are: (1) Can an external attacker force this state on a victim? No — the delegator is always msg.sender in StakeHub.delegate/undelegate/claim, so this is self-selected. (2) Does failed claim() burn funds? No — StakeCredit.claim reverts on transfer failure, rolling back queue pops; BNB stays in StakeCredit, not lost. (3) Is "permanent" proven? Only for immutable contracts exceeding the max cap; the reporter must cite an actual deployed wallet on BSC. This pattern is typically INFORMATIONAL (real compatibility limitation, no attacker-driven impact) rather than REJECTED (which implies no code issue exists).
Felony slash quota is deliberate. maxFelonyBetweenBreatheBlock is a governance-tunable safety valve to prevent mass slashing from client bugs.
Maintenance mode is a trade-off. enterMaintenance() and associated slash/jail thresholds are proportional-downtime design choices, not automatically bypasses.
Deprecated / dead contracts matter. If the vulnerable path depends on deprecated contracts or permanently dead state, the issue may be a real code smell but not a live vulnerability. TokenManager / unbound-token recovery is the canonical example.
Ethereum features disabled on BSC via IsNotInBSC(). BSC adopts some Ethereum hard-fork EVM changes but intentionally disables Beacon-specific machinery. EIP-6110/7002/7251 request processing (deposits, withdrawal queue, consolidation queue) is gated by config.IsNotInBSC() in state_processor.go and miner/worker.go. On BSC, IsNotInBSC() returns false (Parlia != nil), so request collection is skipped, RequestsHash is always EmptyRequestsHash, and BSC block bodies do not carry a requests list. Reports claiming request injection or VerifyRequests no-op attacks must account for this: the injection path does not exist on BSC. However, a no-op verifier for a field that appears in headers is a real code gap (INFORMATIONAL), even if it has no current security impact.
Once the bug is real, answer:
Self-inflicted vs attacker-driven. A critical severity distinction: can an external attacker force the harmful state on a victim, or does the affected party put themselves in that position? If the "victim" is always msg.sender choosing to interact from an incompatible contract, that is a self-selected compatibility issue — typically INFORMATIONAL, not a bounty-severity vulnerability. Check who controls the input address in the affected call path. Also check whether failure reverts cleanly (funds safe but stuck) vs silently commits (funds lost). Revert-on-failure is much less severe than silent loss.
Trace downstream consumers. A bug in helper X matters only if some security-relevant path reads X's output.
RPC / P2P / DoS findings:
httpBodyLimitreceiveRateLimitPerSecondGreenfield endpoint findings:
Cross-chain batch / ACK findings: If earlier submessages commit while later non-crash failures suppress ACKs and retries are impossible, fund lock or permanent state divergence may be real. If atomic commit/rollback or upstream sequence validation already prevents that, downgrade or reject.
TSS findings: Map severity to actual consequence:
| Rating | Criteria |
|---|---|
| P* | Validator selection set manipulation; Merkle proof validation vulnerabilities; remote leaks of unencrypted private keys / mnemonic / key seed |
| P1 | Fund/fee safety; severe token-economy disruption; RCE; key generation / signing / verification vulnerabilities; governance disruption; irreparable consensus splits |
| P2 | Validator DoS; consensus-result/performance disruption; access of disabled cross-chain channels; DoS of cross-chain communication |
| P3 | Explorer / relayer / oracle-relayer DoS |
| P4 | Stability / availability issues in non-critical functions |
When severity is ambiguous, prefer the more conservative rating unless the stronger impact is concretely proven.
Configuration-gated issues: A consensus bug behind a default-disabled experimental flag is usually lower severity until there is proof production nodes enable it.
Useful precedents from past reviews:
eth_getProof key-array resource exhaustionChasingHead poisoningVerifyRequests no-op where BSC disables the entire request pathway via IsNotInBSC() but the verifier gap still exists in the code, or StakeCredit.claim() gas-limit compatibility gap where no attacker can force the state and failure reverts cleanly)magicEnodeID through admin_removePeer)High-severity classes that require proof, not rhetoric:
tss-lib issues that actually recover keys or forge signaturesThere are four verdicts:
P<N> — valid bug with qualifying bounty severityINFORMATIONAL — real code defect or hardening gap, but no qualifying security impact in current code/deploymentREJECTED — not a live vulnerability (false positive, by-design, out of scope, latent+safe, dead code path, etc.)REJECTED (inaccurate as submitted) — real underlying issue, but the trigger analysis / parameter math / impact story is materially wrongUse this format for accepted reports:
Verdict — P<N> Valid bug (<severity label>). <One sentence describing what the attacker can actually do.>
<Report title> (P<N> Assessment)
What the bug does: <1-2 plain-language sentences>
Why P<N> and not higher:
1. <Factor> — <explanation>
2. <Factor> — <explanation>
Best-fit criterion: "<exact criterion text>" — <why it fits>
Severity labels: Critical (P*), High (P1), Medium-High (P2), Medium (P3), Low-Medium (P4).
Use this format for informational findings:
Verdict — INFORMATIONAL Real code defect, but no qualifying security impact in the current code path or deployment.
<Report title> (Informational)
What the code issue is: <1-2 sentences>
Why informational:
1. <Mitigation or backstop> — <explanation>
2. <No exploitable downstream impact> — <explanation>
Use this format for rejections:
Verdict — REJECTED <One sentence saying why.>
<Report title> (Rejected)
Why rejected:
1. <Reason> — <explanation>
2. <Reason> — <explanation>
Use this format for inaccurate submissions:
Verdict — REJECTED (inaccurate as submitted) Valid underlying bug, but the report's trigger analysis or impact story is materially wrong.
<Report title> (Rejected — needs revision)
What the bug does: <1-2 sentences on the real issue>
What needs to be corrected:
1. <Specific error> — <correct analysis>
2. <Specific error> — <correct analysis>
What the reporter must fix to be accepted: <one sentence>
When to choose INFORMATIONAL vs REJECTED: