Normal behavior: docs/DESIGN.md §4 establishes that the moderator's flagOutcome re-flag window stays open until the first claim (claimsStarted), specifically so an honest mistake can be corrected — e.g. flagging SURVIVED and later learning the breach was actually in-scope, correcting to a good-faith CORRUPTED naming the whitehat. §12 guarantees that a good-faith CORRUPTED resolution entitles the named whitehat to the entire pool (snapshotTotalStaked + snapshotTotalBonus).
Specific issue: sweepUnclaimedBonus() is deliberately exempted from setting the claimsStarted finality latch (this is intentional — it stops a 1-wei donation from grief-closing the moderator's correction window). But the function does move real value out of the contract to recoveryAddress. When riskWindowStart == 0, the reserve calculation excludes the bonus entirely, so a single call sweeps the whole bonus B while the re-flag window stays open. A subsequent, legitimate good-faith CORRUPTED correction then re-snapshots totalBonus from its now-zeroed live value, collapsing the whitehat's entitlement from P+B down to just P. The bonus B is permanently stranded at the sponsor-controlled recoveryAddress instead of reaching the whitehat docs/DESIGN.md §12 promises it to.
Likelihood:
This occurs whenever the registry reaches terminal CORRUPTED while the pool never observed an active-risk state (riskWindowStart == 0) — a plausible, non-adversarial scenario for a fast breach or a dormant pool nobody polled during the active-risk window — and the moderator initially flags SURVIVED (explicitly allowed on a CORRUPTED registry per §8) before later correcting to good-faith CORRUPTED after learning the breach was in-scope after all.
The harmful step, sweepUnclaimedBonus(), is fully permissionless and rationally executed by the sponsor (who controls recoveryAddress and directly benefits from the stranded bonus), or can even front-run the moderator's pending correction transaction in the public mempool — it does not require any special privilege or race against unlikely odds.
Impact:
The named whitehat — an external, non-privileged party who is specifically entitled to the whole pool under a good-faith CORRUPTED resolution — is underpaid by exactly one full bonus contribution B, receiving only the staked principal P instead of P + B.
The misdirected bonus is not lost to the protocol but is permanently rerouted to the sponsor-controlled recoveryAddress, meaning the sponsor's own action (or a front-run of the moderator's correction) can retroactively reduce a whitehat's promised bounty with no way for the whitehat or moderator to reclaim it after the fact.
This is the exact, unmodified content of test/poc/F-001-PoC.t.sol. Save it to that path in the project (the test/helpers/BaseConfidencePoolTest.sol base class it inherits from already exists in the repository) and run:
Real captured output:
The correct fix decouples the correction from both claimsStarted and the entitlement snapshot entirely, using a time-based cooldown so value cannot leave the contract until the correction window has meaningfully closed:
This preserves the anti-dust-donation-grief behavior (no claimsStarted change), never inflates bountyEntitlement past the real balance (no brick risk), and closes the actual defect: value cannot leave the contract during the window in which a correction is realistically still pending. If the sponsor prefers not to add new state, an acceptable fallback is to document in DESIGN.md §4/§12 that a SURVIVED-state bonus sweep is irreversible and operationally require moderators to wait a grace period before sweeping — but a code-enforced cooldown removes reliance on off-chain process discipline.
Impact – Medium Up to the entire bonus pool can be routed to the wrong party for good, with no on-chain way to unwind it, and in a stakerless pool the effect is total since bountyEntitlement computes to zero and claimAttackerBounty reverts outright. This impact is definitely not High: the pool stays solvent throughout with no principal ever at risk, and the money ends up at the sponsor's own recoveryAddress, which in most pools means a sponsor recovering a bonus they funded themselves. The whitehat's claim on that bonus also only exists because the moderator changed their mind after the fact. Likelihood – Low Four separate things have to coincide: nobody touches the pool for the whole active-risk window (or there are no stakers to begin with), the moderator flags SURVIVED and later reverses to CORRUPTED, that reversal is good-faith with a named attacker, and a sweep lands between the two flags. The first cuts against staker self-interest, since skipping the poke costs them their entire bonus share, and the second asks the moderator to overturn a scope judgement, which is a bigger deal than the typo fix DESIGN.md #4 offers the window for. The sweep itself I'd treat as near-certain once the rest holds, given it's permissionless and the sponsor has an obvious reason to make the call, but assembling the first three in one pool lifecycle is where this stays rare.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
View preliminary resultsAppeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.