Description:
flagOutcome permits a moderator to re-flag the pool's outcome pre-claim so an incorrect initial judgement can be corrected (ConfidencePool.sol:327):
This is deliberate: a moderator may initially flag SURVIVED on a registry that reads CORRUPTED (permitted — SURVIVED accepts either PRODUCTION or CORRUPTED, ConfidencePool.sol:338, for the case where the breach is judged out of the pool's committed scope), then correct to good-faith CORRUPTED if further investigation shows the breach was in-scope after all. The correction is only blocked once claimsStarted is true.
Separately, sweepUnclaimedBonus is fully permissionless and, when riskWindowStart == 0, does not reserve the bonus and does not set claimsStarted (ConfidencePool.sol:483-490, 503-507):
The does NOT set claimsStarted behavior is intentional and documented — it exists so a trivial 1-wei donation sweep can't accidentally block the moderator's correction window. But nothing distinguishes that intended dust case from a call that sweeps a real, material bonus. Anyone (including the sponsor, who directly controls recoveryAddress and therefore has a direct incentive) can call sweepUnclaimedBonus() in the gap between the initial SURVIVED flag and the moderator's correction. This drains the entire bonus to recoveryAddress and zeroes totalBonus — all while claimsStarted stays false, so the correction is still permitted.
When the moderator then corrects to good-faith CORRUPTED, the re-snapshot re-reads the now-drained value (ConfidencePool.sol:358, 362):
Per the project's own documentation (docs/DESIGN.md §12): "the entire pool is the named attacker's bounty." Because of the sweep, the whitehat's bountyEntitlement is computed short by exactly the bonus amount — which is now permanently sitting at recoveryAddress instead.
Impact:
A named good-faith whitehat attacker — the party the protocol is explicitly designed to reward for reporting an in-scope breach — is permanently and silently shorted the entire bonus portion of the pool. The funds are not recoverable by the attacker afterward; they sit at recoveryAddress, which is sponsor-controlled. Since the sponsor is the direct economic beneficiary of recoveryAddress, the sponsor has a standing incentive to trigger this themselves (or via any address) the moment a SURVIVED flag lands on a CORRUPTED-reading registry, before the moderator corrects it — extracting value that documentation guarantees belongs to the whitehat.
Proof of Concept:
The following Foundry test, run against the actual contract, reproduces the issue end-to-end. Both assertions pass on unmodified code.
Executed trace confirms the exact numbers:
Suite result: 2 passed; 0 failed (includes a control test confirming the same scenario without the sweep correctly pays the attacker the full 140e18, isolating the sweep as the root cause).
Recommended Mitigation:
Prevent the bonus from leaving the pool while the outcome can still be corrected. The minimal fix is to gate the sweep on claimsStarted:
This closes the window without reintroducing the 1-wei-donation griefing concern the current comment describes, since claimsStarted is already set by the first genuine claim on any resolved outcome — a real claimant acting normally, not a donation, is what unlocks the sweep.
An alternative that preserves the current "sweep doesn't need a claim first" behavior: set a one-way bonusSwept latch inside sweepUnclaimedBonus, and have flagOutcome revert a re-flag into good-faith CORRUPTED if that latch is set — forcing an explicit, visible failure instead of a silent under-payment.
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.