Per line 28, a good-faith CORRUPTED outcome entitles the named attacker to the
entire pool (bountyEntitlement = snapshotTotalStaked + snapshotTotalBonus).
flagOutcome is re-flaggable pre-claim (line 322) so the moderator can correct
a mistaken outcome before distribution — this window is gated on claimsStarted,
not on outcome == UNRESOLVED.
The issue: while riskWindowStart == 0, sweepUnclaimedBonus() reserves only
totalEligibleStake, sweeps the full bonus balance to recoveryAddress, and
deliberately does not set claimsStarted. So if the moderator flags SURVIVED
first (valid even when the live registry reads CORRUPTED), anyone can sweep the
bonus before any claim, then the moderator corrects to good-faith CORRUPTED —
the second flagOutcome snapshots totalBonus after it was already reduced.
claimsStarted protects the distribution, but not the accounting inputs
(totalBonus) feeding the next snapshot — a value-movement event can alter
those inputs inside the window the moderator is meant to still control.
Likelihood:
Occurs whenever a moderator's first flag is SURVIVED on a CORRUPTED registry
(e.g. believing the breach out-of-scope, or flagging before full review) —
a normal, expected moderator workflow, not an edge case.
Permissionless: any address can trigger the sweep once the pool is in a
sweep-eligible outcome with riskWindowStart == 0.
Widens naturally where riskWindowStart was never latched — a scenario the
design doc itself acknowledges as real and accepted (§5).
Impact:
Breaks the documented "entire pool" invariant for good-faith CORRUPTED
(§12) — attacker entitlement is silently reduced by whatever bonus swept.
Swept bonus routes to recoveryAddress permanently, with no recovery path
for the moderator or attacker.
Undermines the reflag window's purpose (line 322): meant to let the
moderator fix the outcome before value moves, but value can move without
tripping the claimsStarted guard meant to be the sole finality signal.
This sequence simulates a pool where the registry reports CORRUPTED but the
moderator's first flag is SURVIVED. Between that flag and the corrective
re-flag, an unprivileged caller sweeps the bonus without setting
claimsStarted. When the moderator then corrects to good-faith CORRUPTED, the
new snapshot reflects the already-reduced totalBonus, so the attacker's
bounty comes up short by exactly the swept amount — demonstrating the
entitlement no longer matches the "entire pool" guarantee.
Alternative: have sweepUnclaimedBonus() set claimsStarted = true whenever
it moves nonzero bonus value out of the pool — treating the sweep as the same
finality-latching event a claim is, consistent with §4's rule that value
movement, not caller identity, closes the correction window.
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.