Skip to content

Claiming an unfunded ('open') bounty triggers a real payout from the merchant wallet #35

Description

@AliaksandrNazaruk

Summary (CRITICAL — unfunded bounty triggers a real payout)

POST /api/bounties/[id]/claim lets a bounty be claimed while its status is open, and the claim path then broadcasts a real payout from the merchant web wallet — even though open means the creator's funding was never confirmed.

Details

  • A bounty is created with status = 'open' in POST /api/bounties, and only flips to funded after CoinPay confirms the creator's funding payment (webhooks/coinpay/route.ts sets status='funded' WHERE status='open').
  • The claim guard accepts both:
    if (!['open', 'funded'].includes(bounty.status)) { ... 409 ... }   // line 23
    ...
    WHERE id = ${bountyId} AND status IN ('open', 'funded')            // line 38 (atomic UPDATE)
  • So a bounty whose funding was never completed (creator abandoned the CoinPay checkout, or the payment failed) is still claimable, and the route then calls /api/web-wallet/{MERCHANT_ID}/prepare-tx + broadcast to pay bounty.reward_usd to the claimer.

Impact

The merchant pays out reward_usd for bounties nobody funded. An attacker can create a bounty, never pay, have a second account claim it, and drain the merchant wallet reward_usd at a time.

Fix (PR opened)

Gate claims on status = 'funded' in both the pre-check (line 23) and the atomic UPDATE ... WHERE (line 38, closing the concurrent-flip TOCTOU) so a payout can only fire after the funding webhook has confirmed payment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions