Normal behavior: ConfidencePool ties settlement finality to value movement. The moderator may correct the outcome via flagOutcome until the first claim latches claimsStarted, and docs/DESIGN.md §4 guarantees this is safe — "claimsStarted is a value-movement finality latch" and "Once value has left the contract, a corrective re-flag cannot be honored without breaking balance accounting" / "Finality is correctly tied to value movement."
Specific issue: sweepUnclaimedBonus() moves value out of the contract (the bonus, to recoveryAddress) but does not set claimsStarted. When riskWindowStart == 0 (the "no observable risk" state docs/DESIGN.md §5 relies on), it sweeps the entire bonus under a SURVIVED outcome while leaving that outcome re-flaggable. A moderator correction to good-faith CORRUPTED is then still honored after the bonus has left, snapshots totalBonus == 0, and sets the named attacker's bountyEntitlement to the principal only. docs/DESIGN.md §12 states the good-faith bounty is the entire pool (stake + bonus); here the attacker is paid stake only. This is exactly the "value left the contract, re-flag still honored, accounting broken" state §4 asserts cannot happen — and this value-moving function is permissionless (the sponsor, who owns recoveryAddress, or anyone, calls it). The moderator's SURVIVED→CORRUPTED reclassification is legitimate and supported (§4); no privileged actor performs the harmful step.
Root cause (src/ConfidencePool.sol):
Likelihood:
Occurs when an in-scope breach happens (registry == CORRUPTED) while no pool interaction observed the active-risk window, so riskWindowStart == 0 (the state docs/DESIGN.md §5 explicitly relies on), and the moderator first flags SURVIVED then uses the supported pre-claim correction to re-flag good-faith CORRUPTED.
Occurs through a permissionless call: sweepUnclaimedBonus() needs no privileged role, so the sponsor (owner of recoveryAddress) triggers the sweep inside the correction window. All registry transitions involved are forward and reachable.
Impact:
The named good-faith attacker receives only the principal instead of the entire pool (stake + bonus) they are owed per docs/DESIGN.md §12; the full bonus pool is lost to them and paid to recoveryAddress.
A supported correction (docs/DESIGN.md §4) produces a broken payout — the exact accounting break §4 states cannot occur once value has left.
Add as test/poc/SweepReflagBonusLeak.t.sol; run forge test --match-path test/poc/SweepReflagBonusLeak.t.sol -vvv. Forward-only, reachable registry transitions.
Output:
Treat sweepUnclaimedBonus as a value-movement finality event for re-flag purposes: once the bonus has left, block a re-flag that would rely on it (the guarantee §4 states), without latching claimsStarted on the claim paths (preserving the intended 1-wei donation protection).
Alternatively, reserve the bonus in sweepUnclaimedBonus while the live registry reads CORRUPTED under a still-correctable SURVIVED, so a good-faith CORRUPTED correction can still pay the whole pool.
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.