TreasureHunt.withdraw() at line 223 has no onlyOwner or equivalent guard, unlike every other admin function. Once claimsCount >= MAX_TREASURES, any external caller can trigger it. Funds always flow to owner so there is no direct theft, but the owner loses control of when the withdrawal lands on-chain.
Compare with fund() (237), pause() (246), unpause() (255), updateVerifier() (265), emergencyWithdraw() (275) — all explicitly require msg.sender == owner.
Likelihood: moderate. A malicious 10th-claim recipient can reenter withdraw() from their fallback; with no donations the balance is zero at that moment and the inner require(balance > 0) reverts, but any donation via receive() makes the reentrant call succeed. Impact: forced timing of the owner's payout transaction (tax, multisig signing windows). No direct fund loss.
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.