Sibling admin functions in the same contract (fund(), pause(), unpause(), updateVerifier(), emergencyWithdraw()) all enforce msg.sender == owner, and the NatSpec above withdraw() says "Allow the owner to withdraw unclaimed funds after the hunt is over." The intent is clearly owner-only.
withdraw() itself has no caller check. Any address can invoke it once claimsCount >= MAX_TREASURES, forcing the contract to transfer its remaining balance to owner. Funds are not at risk (the transfer target is still owner), but the owner loses control over the timing and gas price of the finalization event, enabling griefing and off-chain bookkeeping confusion.
Likelihood:
Consistently callable by any address once claimsCount == MAX_TREASURES. No special conditions required; the first external observer who notices the hunt has ended can invoke it.
Easy to automate: a watcher bot subscribing to Claimed events can race to call withdraw() the moment the tenth claim confirms.
Impact:
No theft of funds — owner remains the transfer destination hardcoded on line 228.
Griefing: a non-owner forces the owner to bear the cost of a gas-price-of-their-choosing finalization log, or emits the Withdrawn event at an inconvenient moment (e.g. right before a multi-tx owner batch).
Off-chain automation that assumes Withdrawn is emitted from a transaction where tx.origin == owner (for ACL, cron, or notification purposes) misclassifies the event.
Minor searcher MEV risk: generic hunters who earn rewards for emitting specific events may front-run the owner's finalization.
Deploy the contract, fund it with 100 ETH, and simulate ten valid claims (or use vm.deal and direct storage writes in Foundry to set claimsCount = MAX_TREASURES). Then, from a non-owner EOA, call hunt.withdraw(). The call succeeds, transferring the entire remaining balance to owner and emitting Withdrawn from a transaction the owner did not authorize.
Add the owner check already used by sibling admin functions. The contract even declares the matching error OnlyOwnerCanFund but never uses it, and the existing onlyOwner modifier on line 53 is the idiomatic fit.
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.