pokeRiskWindow() seals riskWindowStart after the coverage term, force-settling a survived pool CORRUPTED and sweeping 100% of staker principal to recoveryAddress.A pool whose agreement stays non-terminal through expiry survived its coverage term and must resolve EXPIRED, returning principal + bonus (docs/DESIGN.md §2/§5). §5 states the auto-CORRUPTED backstop "cannot apply when no risk window was observed."
riskWindowStart is overloaded: for bonus it means "a risk window was observed"; for principal (claimExpired L532) it means "risk materialized in-term" — nothing enforces the second. _markRiskWindowStart caps the seal to expiry yet still writes non-zero, and pokeRiskWindow() is permissionless with no block.timestamp < expiry guard (it blocks only post-resolution). A poke during a post-term active-risk state therefore seals riskWindowStart = expiry (non-zero) with zero in-term risk, arming the CORRUPTED sweep against a survivor.
Not the §6 accepted case: §6 exempts an out-of-scope breach (spatial) observed in-term; this is a post-term breach (temporal), which §2 defines as EXPIRED. §6's "a poke does not manufacture it" is disproven by the control (no poke → EXPIRED) vs exploit (one poke → swept). The fix is lossless, confirming an unintended conflation.
Likelihood:
Triggers when a pool survives its term, the agreement is breached after the term, and any address calls the permissionless pokeRiskWindow() before claimExpired; the principal sweep additionally needs the moderator absent for the 180-day grace (the backstop condition).
The bonus-misdirection facet needs no moderator absence and no breach — only a post-term active-risk state plus a poke.
Impact:
100% of staker principal (+ bonus) is swept to the sponsor-controlled recoveryAddress for a pool that won its bet; the outcome is irreversibly latched.
Unclaimed bonus that §5 routes to recoveryAddress is instead paid to stakers (the globalScore == 0 fallback) — value misdirected.
test/unit/PocPostExpiryPokeSweep.t.sol (5/5) and test/fork/PocPostExpiryPokeSweep.fork.t.sol (2/2, live BattleChain testnet chainId 627 — real SafeHarborRegistry/AttackRegistry/Agreement via the real factory path; only getAgreementState's state sequence is vm.mockCall'd). Each exploit is paired with a passing control:
Gate the CORRUPTED backstop on a dedicated in-term flag, leaving riskWindowStart (and its bonus clamp) untouched.
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.