DEV Community

Quinn
Quinn

Posted on

We put a $300 bounty on Pink's AI agent spending rules. It was gone by lunch.

We build Pink Agentic AI Payments, an MCP server and REST API that lets an AI agent request a payment, but checks that request against rules a company set up first: budgets, amount limits, which payees are allowed or blocked, what hours it can run, which human has to sign off. Only after a request clears those rules does a single-use credential get issued, locked to that payee and that amount. It's a public sandbox, test money only, nothing in production yet. You can poke at it yourself at agentic-sandbox.pinkwallet.com/demo.

On October 7 we opened a public challenge: break the rules. Get an agent, or anything else, to push a payment through that the rules should have stopped. The repo is here. On October 8 we put money behind it, $300 total, $100 each for the first three verified wins.

We expected to wait a few days. We waited about 4.5 hours.

The whole bounty, gone before lunch

All three issues landed on a Saturday, October 10, between 08:50 and 12:18 Pacific time. Nobody used an AI agent to do it. Nobody touched prompt injection. Every single win was plain curl.

Issue #1: a trailing space in "EUR "

Reported by @ins0x4nur4g. One of our rules says payments over $1,000 need CFO approval. The reporter sent a EUR 999 payment, except the currency field was "EUR ", with a trailing space. Our pricing engine didn't recognize the padded code, fell back to treating it as 1:1 with USD, and approved it as a $999 payment. No CFO needed. At the sandbox's rate, EUR 999 is about $1,079, over the line.

POST /v1/payments
Content-Type: application/json

{
  "payee_id": "p_studio",
  "amount": 999,
  "currency": "EUR "
}
Enter fullscreen mode Exit fullscreen mode

That trailing space was the entire exploit.

We fixed it about 70 minutes after the report. Currency has to be exactly one of USD, EUR, GBP, HKD, SGD or JPY, or the request gets an HTTP 400. The engine no longer prices anything it doesn't recognize; it rejects instead of guessing. Our write-up of win #1 has more detail.

Issue #2: whose clock is it anyway

Also @ins0x4nur4g, same morning. We had a rule that read "outside 06:00 to 23:00 SGT, finance on-call decides." The rule was written in Singapore time. The server checked it in UTC. At 02:07 Singapore time, which is well inside the quiet window, a $1,000 supplier reorder sailed through, because the UTC clock said it was fine. On top of that, a payment request could pass a local_hour field and just pick whatever hour it wanted.

Worth being honest about one detail: the reporter's specific reproduction used an agent that this particular time rule didn't even apply to. But the underlying bug, UTC versus workspace time, was real and reproduced cleanly on the agents the rule did cover.

Fixed the same day. Every workspace now has a timezone attached, shown right there in the rules and in the request traces, and time rules evaluate on the server's clock in that timezone. local_hour still exists, but it only works on dry runs, never on a real payment.

Issue #3: half a cent, and we're paying anyway

Reported by @kimutaiRop, a statistician. One of our rules auto-allows anything at or under $500. A payment of $500.0001 cleared it. Why: the rule compared against the amount rounded to the cent, but the credential that actually got issued carried the unrounded figure. The gap between what the rule thought it approved and what the credential was worth: under half a cent.

The reporter said so themselves, that the real-world impact here was tiny. They were right. We're paying the $100 anyway, because our own challenge rules say a credential for an amount a rule should have blocked counts as a win, full stop, regardless of how small the number is. If we only pay out for scary bugs, we're training people to stop reporting the small ones, and the small ones are exactly how you learn the comparison logic is wrong.

The fix: any amount with more decimal places than its currency allows now gets rejected with an HTTP 400. That one is still being fixed, not done yet.

What three very different bugs have in common

Issue What broke What we changed
#1 trailing space in currency Unknown currency code silently priced 1:1 as USD Currency must exactly match an allow-list (USD, EUR, GBP, HKD, SGD, JPY) or the request is rejected (HTTP 400)
#2 UTC vs. workspace time Time-of-day rule checked against server UTC, not the rule's stated timezone; caller could also pass local_hour Every workspace has a declared timezone; rules evaluate in it; local_hour only works on dry runs
#3 rounding mismatch Rule compared cent-rounded amount, credential carried the unrounded amount Amounts with more decimal precision than the currency allows are rejected (HTTP 400); in progress

Nobody attacked the AI. Nobody needed to. In all three cases the rule logic itself was correct: the $1,000 approval threshold was fine, the SGT quiet hours were fine, the $500 auto-allow was fine. What failed was the input the rules were trusting: a currency string that wasn't validated, a clock that wasn't the one the rule meant, a decimal that got rounded in one place and not another. The AI agent is not the only attacker surface here. The edges of the system are.

What we'd tell anyone else building agent payments

  • Validate at the edge with an allow-list, not a fallback. "Unrecognized input, treat it as the default" is how a currency typo becomes a 1:1 exchange rate.
  • Take time from the server, never from the caller, and make sure the server's notion of "the server" matches the timezone your rule was actually written in.
  • Evaluate the rule and issue the credential against the exact same normalized value. If you round for one and not the other, the gap between them is free money.
  • Pay out by your own rules, even when the bug is small enough to be almost embarrassing to claim. The $0.0001 report was the cheapest, least dramatic one, and it still pointed at a real rounding bug.

The gap we know about and haven't closed

We're not going to pretend the challenge is over. There's a known, documented hole we haven't fixed: splitting one payment into several smaller ones, each of which passes its own amount rule, can still drain a budget that a per-payment rule alone doesn't catch. Only a per-day rule or a budget cap stops it, and not every workspace has one configured. We know this because we ran our own benchmark against it: Claude did exactly this split-payment move in 2 of 80 runs. The benchmark is public too.

Still open

The $300 is claimed, but the challenge isn't closed. There's no more bounty money, but Hall of fame credit and a public write-up are still up for grabs for anyone who finds the next one. If you want to try:

Disclosure: Pink Agentic AI Payments is a product of PinkWallet; all three reporters disclosed through our public GitHub issue tracker and are being paid through the bounty described above.

Top comments (0)