ConfidencePool validates and permanently trusts its agreement parameter using the pattern Battlechain's own documentation names and flags as unsafe:
From the ConfidencePoolFactory::createPool and the ConfidencePool::initialize
From the ConfidencePool::_replaceScope
From the ConfidencePool::_getAgreementState
From this part of the docs it states that:
The legacy three-step check shown in older versions of these docs — safeHarborRegistry.isAgreementValid(...), attackRegistry.isTopLevelContractUnderAttack(...), agreement.isContractInScope(...) — is not
safeif agreement was retrieved from BattleChainSafeHarborRegistry. All three checks can return true for an agreement that isn't the Binding Agreement, exposing you to different terms than you read. Always resolve via theAPIorBCQuery.
The Safe Harbor repos own known-issues.md may have confirmed that this as a named charateristic of the system:
" A protocol can deploy multiple factory-validated agreements and register different ones with each registry... The same divergence can arise unintentionally"
ConfidencePool never performs Binding Agreement resolution, the agreement is actually accepted as a raw parameter at initialize(), validated only through the documented unsafe checks and trusted for the pool's entire lifecycle as the source of the registry state driving every deposit gate and resolution path.
What may actually elevate this finding beyond a sponsor-trust question:
The AgreementFactory::create function is fully permissionless, and owner is a caller-supplied parameter not derived from msg.sender
With keen observation, the agreement's constructor that has been highlighted above validates chain IDs against REGISTRY.isChainValid and internal consistency only, it never verifies the deployer or owner actually controls the account address being listed in scope.
This actually means that anyone, with zero relationship to a real protocol, can deploy an agreement listing that protocol's genuine contract addresses in its scope, name themselves as owner and attack moderator by default, and it will pass every check ConfidencePool performs.
This is no longer the usual legitimate sponsor acting carelessly or maliciously shenanigans that get invalidated on the codehawks platform. This is a fully permissionless attack path for any unrelated third party and critically since the attacker owns the decoy agreement, they are also its default attackModerator thus giving them the ability to drive that agreement's own registry state (including to CORRUPTED) entirely independently of the real protocol.
The attached POC demonstrates the full attack chain end to end, not just pool creation:
An unrelated third party deploys their own agreement listing a real protocol`s contract address in scope.
Creates a fully attacker owned ConfidencePool through the legitimate factory flow (recoveryAddress pointed at their own wallet).
A staker deposits real capital believing they're insuring the displayed real protocol.
The attacker then drives their own decoy agreement to CORRUPTED, the pool's legitimate moderator, observing a genuingly CORRUPTED registry state for the agreement this pool was configured with, flags the outcome as bad-faith CORRUPTED, and the stakers entire principal(deposit) is swept to the attacker's recoveryAddress, all while the real protocol displayed in the pool's scope was never touched and remains completely unfazed.
From this wonderful and robust explanation that has been made above, this directly undermines the product's core premise which states: stakers economically signal their belief that the in-scope contracts will survive the agreement term, because the resolution stakers are paid out on is not guaranteed to reflect the real outcome for the contracts displayed, and here actively enables a fund-loss path for stakers with no fault on the moderator's part.
Kindly place this into test/ and name the file PermissionlessDecoyAgreement.t.sol.
and run
If IAttackerRegistry exposes getAgreementForContract, have ConfidencePool._replaceScope cross-check that agreement == attackRegistry.getAgreementForContract(account) for each top level scope account at scope-set time thus rejecting any account whose true Binding agreement doesnt match the pool`s agreement.
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.