claimExpired() gives the moderator a 180 day grace period before permissionless auto-CORRUPTED can finalize a pool as bad-faith CORRUPTED. That grace is anchored to pool expiry, not to the first time terminal CORRUPTED becomes visible.
This creates a classification gap when an agreement remains in an active-risk state beyond expiry + MODERATOR_CORRUPTED_GRACE and only later becomes terminal CORRUPTED. Before terminal CORRUPTED, the moderator cannot flag the pool as CORRUPTED because flagOutcome(CORRUPTED, ...) requires the registry state to already be CORRUPTED. Once the terminal state appears after the expiry-anchored grace has elapsed, any caller can immediately call claimExpired(), lock bad-faith CORRUPTED, set claimsStarted, block moderator good-faith classification, and allow the full pool to be swept to recoveryAddress.
The issue is not that auto-CORRUPTED exists. The design notes accept an expiry-anchored fallback for moderator unavailability, but this case is different: terminal CORRUPTED was not flaggable at all until after the whole expiry-anchored grace period had already elapsed. The moderator receives no actionable grace period after terminal corruption first becomes visible.
The moderator path in ConfidencePool.sol::flagOutcome cannot classify CORRUPTED before the registry itself is terminal CORRUPTED:
While the registry is still UNDER_ATTACK or another active-risk state, the moderator cannot flag CORRUPTED.
The auto-CORRUPTED branch in ConfidencePool.sol::claimExpired treats any nonzero risk window as eligible for bad-faith finalization:
The auto-CORRUPTED delay checks only expiry + MODERATOR_CORRUPTED_GRACE. It does not consider when CORRUPTED first became observable.
After bad-faith finalization, ConfidencePool.sol::claimCorrupted routes the remaining pool balance to recovery:
Bad-faith CORRUPTED then routes the full pool balance to recovery.
Likelihood: Low
A pool observes active risk before expiry, so riskWindowStart != 0.
The agreement remains active-risk until after expiry + MODERATOR_CORRUPTED_GRACE.
The registry first becomes terminal CORRUPTED only after that expiry-anchored grace has already elapsed.
A caller invokes claimExpired() before the moderator can submit a good-faith classification.
Impact: High
A valid good-faith attacker can lose the entire bounty because the mechanical path sets claimsStarted before the moderator can name the attacker.
If the late terminal CORRUPTED is unrelated to the pool's committed scope and the moderator would have classified the pool as SURVIVED, staker principal and bonus can be swept to recoveryAddress through bad-faith CORRUPTED.
The moderator has no usable post-terminal review window even though CORRUPTED was not flaggable before the terminal transition.
The PoC file included with this report is:
To run it in a fresh contest checkout:
Copy LateTerminalCorruptedNoFreshGrace.t.sol into the repository's test/ directory.
Run:
Expected result:
The PoC demonstrates:
If the registry remains UNDER_ATTACK until after expiry + MODERATOR_CORRUPTED_GRACE, then flips to CORRUPTED, a permissionless claimExpired() immediately locks bad-faith CORRUPTED.
The locked path blocks moderator reflagging, denies any attacker bounty, and lets claimCorrupted() sweep all stake plus bonus to recovery.
Inline PoC source (LateTerminalCorruptedNoFreshGrace.t.sol):
Track when terminal CORRUPTED is first observed and start the moderator grace from the later of pool expiry and first terminal visibility.
This preserves the unavailable-moderator backstop while ensuring the moderator receives an actionable window after terminal corruption is visible.
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.