ConfidencePoolFactory.createPool() allows the owner of a valid Safe Harbor agreement to create a pool, makes that address the pool owner, and stores the recovery address chosen by that sponsor. A separate factory-configured outcomeModerator is intended to decide whether a pool's local scope survived or was corrupted.
At expiry, ConfidencePool.claimExpired() reads the agreement's live registry state. A CORRUPTED state observed after an active-risk window normally gives the outcome moderator 180 days to decide the pool outcome. When no outcome is flagged during that period, anyone can mechanically finalize bad-faith CORRUPTED; claimCorrupted() then sends the complete remaining pool balance to the sponsor-selected recovery address.
The factory guarantees that the canonical pool sponsor is the Safe Harbor agreement owner. In the pinned Safe Harbor integration, registration initializes that agreement owner as the per-agreement attackModerator. That role can call markCorrupted() from an active-risk state; the on-chain checks verify the role and lifecycle state, but do not establish that an attack succeeded.
Consequently, the pool sponsor can convert its upstream operational/reporting authority into a pool-principal forfeiture authority. This works even when the pool's outcomeModerator is a distinct DAO address: the sponsor makes the agreement read CORRUPTED before the staker resolves expiry, waits out the moderator grace period, and lets any address finalize the mechanical bad-faith outcome. The full pool is then transferred to the sponsor's recovery address.
This report does not claim a flaw in the out-of-scope Safe Harbor contracts. They provide the actual authority model that the in-scope pool consumes. It also does not report the documented scope-blind auto-CORRUPTED fallback by itself. The documented behavior assumes that a terminal registry CORRUPTED state is a trustworthy proof of a real breach; the missing boundary is that this state can be authored by the pool sponsor, who is also the recovery beneficiary, without the pool's independent moderator deciding the pool-specific outcome.
The out-of-scope Safe Harbor integration establishes the authority overlap consumed above:
Likelihood: Medium
The canonical factory path creates this overlap whenever a Safe Harbor agreement owner sponsors a pool and keeps its default agreement-level attack-moderator role. The tested Safe Harbor registration initializes attackModerator to agreementOwner.
The exploit path occurs during the documented UNDER_ATTACK or PROMOTION_REQUESTED lifecycle after DAO approval, once a public pool interaction has recorded riskWindowStart.
The principal sweep completes after the independent outcomeModerator has not flagged a corrective result during the fixed 180-day grace period. That meaningful operational/timing condition keeps likelihood at Medium rather than High.
Impact: High
The sponsor-selected recovery address receives the entire live pool balance, not merely the attacker's own deposit. The balance includes all unresolved staker principal and the contributor-funded bonus.
Mechanical finalization sets claimsStarted, so the independent outcome moderator cannot re-flag the result after the grace period. A staker's expiry claim has already been blocked by the sponsor-created CORRUPTED state.
The local runtime PoC transfers exactly 100 stake tokens plus 50 bonus tokens to the sponsor. The loss scales with the full balance of each affected pool.
Add the following test as test/poc/CodexGpt5_20260710_SponsorAttackModeratorAutoSweep.t.sol and run:
Observed runtime result on the current 58e8ba4ce3f3277866e4926f3140e597f9554a1e source:
An additional non-broadcast fork test at BattleChain testnet block 16000 confirms the real integration authority: the demo agreement owner and its live getAttackModerator() value are both 0x3EfcFc441c1e70A141850D4da0d5C7314952Fc3d; that account successfully calls the real registry's markCorrupted() in the fork. The subsequent locally deployed in-scope factory pool transfers 100 stake plus 50 bonus tokens to the configured sponsor recovery account. The fork test passes with 1 passed, 0 failed, 0 skipped.
Do not create a bad-faith, principal-confiscating pool outcome from an agreement-level state that may be authored by the pool sponsor. The smallest safety-first change removes the permissionless auto-CORRUPTED finalization and leaves this decision to the independent outcomeModerator.
For a permanent-moderator-unavailability escape hatch, introduce a factory/DAO-controlled fallback resolver or a registry attestation whose authority cannot overlap with the agreement owner or recovery beneficiary. That resolver can finalize the existing bad-faith branch after the grace period. Do not restore a permissionless full-pool sweep based only on getAgreementState(agreement) == CORRUPTED.
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.