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.
Appeals are being carefully reviewed by our judges.
The contest is complete and the rewards are being distributed.