Early rate$2,400 of senior audit time for $500. Early members keep the rate as it climbs.$2,400 of senior audit time for $500See how →
F-2026-0011·access-control

`openCase` admits unauthenticated, attacker-authored escrows

Fixedescrowarbitrationdispute-resolution
TL;DR

openCase trusts state self-reported by the escrow it is validating, with no check against the factory. A hostile contract can open cases at will, burning protocol-paid VRF requests, locking genuine jurors onto fake panels and draining the fee pool.

Severity
HIGH
Impact
HIGH
Likelihood
MEDIUM
Method
MManual review
CAT.
Complexity
MEDIUM
Exploitability
MEDIUM
02Section · Description

Description

The pool holds no reference to an approved factory or escrow registry. openCase accepts an arbitrary address and treats that contract's self-reported state as authoritative:

solidity
if (escrow.arbitrator() != address(this)) revert NotEscrowArbitrator();
if (escrow.status() != ESCROW_STATUS_DISPUTED) revert NotDisputed();
...
uint256 dealValue = escrow.disputeBaseAmount();
bool commercial = escrow.commercial();

Both guards are attested by the contract being validated. A hostile contract returns the pool's own address and status 3. CaseAlreadyExists is sidestepped by returning a fresh disputedMilestoneIndex each call. dealValue and commercial — which choose the tier lane and the fee size — are likewise attacker-chosen.

The fabricated case consumes a genuine VRF request, locks a real panel's activeAssignments, and on tally pays every matching juror the tier fee from the global feePool — up to 35,000 cNGN per juror in the commercial lane — which requires Expert jurors to be staked, since that lane also gates eligibility to tier 2; against a Community-only registry the drain is 6,000 cNGN per case — for a case that contributed no compensation budget. Payment is credited to juror stake and is withdrawable via unstake.

Where the attacker also controls the eligible jurors in the targeted lane, this is deterministic rather than probabilistic: SelectLib#L94-L96 returns the entire eligible set with no sampling when eligibleCount <= panelSize.

03Section · Impact

Impact

Unconditionally: anyone can burn protocol-paid VRF requests and lock full panels of genuine jurors, removing them from real disputes, while driving feePool toward zero. Genuine jurors on later legitimate disputes are then paid partially or not at all (JurorFeePartiallyPaid). Conditionally on lane control: the drained fees become attacker-owned, withdrawable stake, and their collateral is never slashed because the controlled panel votes unanimously.

04Section · Recommendation

Recommendation

Store an immutable or explicitly governed set of factory addresses and verify the submitted escrow is registered by one of them with this pool recorded as its arbitrator; or require one-time escrow registration with reciprocal dependency checks at creation. Snapshot the fee-driving fields (dealValue, commercial, parties) from authenticated factory/escrow state, never from the submitted contract's return values.

05Section · Resolution

Resolution

Fixed. Escrow provenance is now checked against the factory registry; a fabricated escrow is rejected before it can consume a VRF request or lock a panel.

06Section · Affected files

Affected files

  • ArbitrationPool.sol#L545-L568
  • lib/ArbitrationPoolSelectLib.sol#L92-L96
  • lib/ArbitrationPoolTallyLib.sol#L179-L193
Status
Fixed
F-2026-0011