Normal behavior: When the moderator flags good-faith CORRUPTED and names a whitehat, bountyEntitlement is set to the entire pool (snapshotTotalStaked + snapshotTotalBonus), and the named whitehat is entitled to claim all of it. The re-flag correction window is deliberately kept open until the first real claim — sweepUnclaimedBonus() intentionally does NOT set claimsStarted so a stray sweep cannot prematurely close that window.
Issue: When riskWindowStart == 0, sweepUnclaimedBonus() treats the ENTIRE bonus pool as unowed and sweeps it to recoveryAddress while leaving claimsStarted false. If this runs after a SURVIVED flag but before the moderator corrects the outcome to good-faith CORRUPTED, the re-flag succeeds (no revert) but recomputes bountyEntitlement from the now-drained totalBonus — permanently shorting the whitehat by the swept amount, which is stuck at the sponsor-controlled recoveryAddress.
Likelihood:
The registry reaches terminal CORRUPTED without any pool interaction ever observing an active-risk state, so riskWindowStart stays 0 (DESIGN.md §5 confirms this state is reachable).
The moderator's first flag is SURVIVED (an out-of-scope-breach judgment) and is later corrected to good-faith CORRUPTED — the exact re-flag correction workflow the contract supports.
Any address calls the permissionless sweepUnclaimedBonus() in the window between the two flags; the sponsor who controls recoveryAddress gains the swept amount and needs no privilege to trigger it.
Impact:
The named whitehat receives the pool minus the swept bonus (e.g. 100 instead of 120) — a direct, permanent shortfall of their good-faith bounty.
The swept bonus is stuck at recoveryAddress; claimCorrupted() reverts NothingToSweep(), so it is unrecoverable, not merely delayed.
This PoC contains two tests with identical setup — 100 staked, 20 bonus, riskWindowStart == 0, registry reads CORRUPTED. The only difference is that the exploit test inserts an interim SURVIVED flag followed by a permissionless sweepUnclaimedBonus() before the good-faith CORRUPTED flag; the control does not. With the sweep, the whitehat's bounty is computed as 100; without it, 120. This isolates the sweep-then-reflag sequence as the sole cause of the 20-token shortfall. Both tests pass (2 passed; 0 failed).
The fix blocks sweepUnclaimedBonus() only in the exact state where the outcome is still correctable toward good-faith CORRUPTED — a SURVIVED outcome with no observed risk window while the live registry still reads CORRUPTED. This prevents the bonus from leaving before the moderator's re-flag sizes bountyEntitlement, while leaving all normal dust/excess sweeps untouched.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.