Normally, once value leaves the pool after resolution, the claimsStarted latch is set so the moderator can no longer re-flag the outcome — this ties resolution finality to value movement, and every claim/sweep path honors it. For a good-faith CORRUPTED outcome, the whitehat attacker is entitled to the whole pool (stake + bonus).
However, sweepUnclaimedBonus() deliberately does not set claimsStarted. When riskWindowStart == 0 it treats the full bonus as unreserved and sends it to recoveryAddress, yet leaves the re-flag window open. So the moderator can flag SURVIVED → anyone sweeps the entire bonus to recovery → moderator corrects to good-faith CORRUPTED, which recomputes bountyEntitlement from the now-drained totalBonus. The whitehat is paid stake only, and the bonus is permanently misdirected to recoveryAddress.
Likelihood:
Occurs whenever the registry reaches CORRUPTED with riskWindowStart == 0 (no active-risk state was ever observed on-chain), which is the ordinary "no observable risk" case.
Occurs whenever the moderator flags an interim SURVIVED and later corrects it to good-faith CORRUPTED, while anyone calls the permissionless sweepUnclaimedBonus() in between (e.g. the sponsor who controls recoveryAddress).
Impact:
The entire bonus pool is permanently misdirected to recoveryAddress instead of the whitehat attacker who is owed the whole pool.
The whitehat is underpaid — receiving stake only — and no path returns the swept bonus, defeating the design's guaranteed pre-claim re-flag correction.
Add the following test to test/unit/BonusSweepReflagMisdirection.t.sol and run
forge test --match-test test_EXPLOIT_sweepBeforeReflag_strandsBonus -vv:
Output:
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.