riskWindowStart is the on-chain marker that the pool observed the agreement in an active-risk state. It is sealed lazily: any entrypoint that observes the pool during UNDER_ATTACK or PROMOTION_REQUESTED calls _markRiskWindowStart. Whether it is sealed later decides which branch claimExpired takes for a terminally CORRUPTED agreement that the moderator has not flagged: state == CORRUPTED && riskWindowStart != 0 auto-resolves CORRUPTED (whole pool sweeps to recovery), otherwise it falls through to EXPIRED (stakers refunded).
pokeRiskWindow (ConfidencePool.sol:672) carries no access-control modifier, so any address, not just a staker or the moderator, can seal riskWindowStart at a moment of its choosing while the pool is in an active-risk state. It also runs while the pool is paused. When the moderator is slow or absent, an unprivileged caller sealing (or declining to seal) the window is what selects the staker-favorable versus recovery-favorable resolution, so a resolution-affecting variable is under the control of arbitrary callers rather than a privileged role.
Likelihood:
Any address can call pokeRiskWindow while the pool is in UNDER_ATTACK / PROMOTION_REQUESTED, since the function has no access-control modifier and no pause gate (ConfidencePool.sol:672).
The branch it influences is reached whenever the agreement reaches terminal CORRUPTED and the moderator has not yet flagged, which is the same window the pool is designed to backstop.
Impact:
An unprivileged caller, not a privileged role, effectively selects whether a post-expiry CORRUPTED pool auto-resolves to a whole-pool recovery sweep or to a staker refund. The value moved is real (the full pool), even though the caller gains nothing directly.
Bounded to Low because the recovery destination is the sponsor address (no attacker profit) and the harmful outcome additionally requires an absent moderator, which sits inside the protocol's trusted-moderator assumption.
The test shows pokeRiskWindow is callable by an arbitrary address and even while paused, and that the sealed vs unsealed riskWindowStart selects the CORRUPTED-sweep vs EXPIRED-refund branch of claimExpired.
Output (forge test --match-contract ChainEscalate -vv, forge 1.7.1):
Either restrict pokeRiskWindow to the moderator (or the pool owner), or fold the observation into the state-changing entrypoints that already run it so there is no standalone unprivileged sealing lever, so the moment riskWindowStart seals is not selectable by an arbitrary third party.
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.