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.
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.