When a pool is created, createPool() is expected to guarantee that the pool's scoped accounts are genuinely insured by the agreement — so that a staker reading the pool on-chain can trust that the pool represents the real protocol. The validation checks that the agreement was factory-deployed, that the caller owns the agreement, and that each scoped account is listed in the agreement's scope.
However, none of these checks verify that the agreement is the canonical/binding owner of the scoped contracts. isAgreementValid() returns true for any factory-deployed agreement, and the agreement's scope is fully attacker-controlled. An attacker can therefore create their own agreement that lists real protocol contracts, pass every check, and stand up a pool that is on-chain indistinguishable from the legitimate one — while the attacker controls the moderator role and recovery address.
Likelihood:
Any address can call AgreementFactory.create() with a scope listing real protocol contracts and then call createPool() — no special permissions, no cost beyond gas.
Real and fake pools expose identical scope arrays and emit identical ScopeUpdated events, so stakers relying on on-chain data cannot tell them apart and will deposit into the spoofed pool.
Impact:
The attacker, being pool owner, moderator, and recovery address, can flag CORRUPTED and sweep the entire pool balance to themselves — total loss of all staker deposits.
The attack is repeatable against every protocol, permanently undermining trust that any pool on-chain represents its claimed protocol.
Verify that the agreement is the canonical (binding) owner of each scoped contract, using IAttackRegistry.getAgreementForContract().
In src/interfaces/IConfidencePool.sol:
In _replaceScope() in src/ConfidencePool.sol:
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.