DESIGN.md (§8) states that "stakers' exposure is bounded by what they signed up for at deposit time." In other words, once a user deposits, the covered risk should remain consistent with what they accepted when staking.
This guarantee is enforced for expiry: the first stake permanently locks it through expiryLocked. However, the same protection does not exist for the pool scope. scopeLocked is only set once the registry leaves the NOT_DEPLOYED / NEW_DEPLOYMENT states.
As a result, the sponsor can still call setPoolScope() after users have already deposited, changing the covered contracts while active stakers remain in the pool. Nothing on-chain notifies or requires existing stakers to acknowledge this change before the scope becomes immutable.
Impact:
Once the registry leaves NOT_DEPLOYED/NEW_DEPLOYMENT, the scope locks permanently. When the risk window opens, withdraw() is permanently disabled — a staker who didn't notice the scope change is now trapped with no exit and exposure to contracts they never evaluated.
A malicious sponsor can bait deposits with a robust scope, silently swap it for a sponsor-controlled fragile contract before the lock, trigger a corruption on that contract, and drain the entire pool via claimAttackerBounty().
I will accept a downgrade to Low if "stakers' exposure is bounded by what they signed up for at deposit time" is considered inaccurate wording that conflicts with the explicit §8 statement that "the sponsor can update scope freely while the registry is in
NOT_DEPLOYED/NEW_DEPLOYMENT", making this a documentation inconsistency rather than a security issue.
Likelihood:
Becoming a pool sponsor only requires calling ConfidencePoolFactory.createPool() with a valid BattleChain Agreement.
The replacement scope must belong to the Agreement's existing scope, so the sponsor cannot introduce arbitrary contracts. The attack consists of switching from the advertised robust scope to a riskier — or sponsor-controlled — contract already listed in the same Agreement.
Add this function to this file /test/unit/ConfidencePool.scope.t.sol:
Then run forge test --mt testActiveStakerScopeCanBeChangedBeforeScopeLock -vvv
Two possible solutions:
1. Lock scope on first stake, mirroring the expiryLocked pattern:
2. Remove the contradictory invariant from the design doc if the current behavior is intentional:
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.