AI Payment Security: Three Bugs Found in a $300 Bounty13 min read2,507 words

AI Payment Security: Three Bugs Found in a $300 Bounty

Three simple API bugs bypassed AI payment rules in a public bounty. See what failed, how the team responded, and what to check before trusting agent payments.

AI payment securityagentic paymentspayment API securityAI agent spending limitssecurity bounty
AI Payment Security: Three Bugs Found in a $300 Bounty

Three Simple Bugs Bypassed AI Payment Rules. Here's What They Exposed

An AI agent doesn't need a fancy prompt to misuse a payment system. Sometimes it's the boring stuff - like a malformed currency code or a clock set to the wrong timezone - that does the damage.

That was the lesson from a public security challenge run by Pink Agentic AI Payments. The company offered $300 for verified ways to make a payment that its rules should have blocked. The money was claimed in about four and a half hours. All three winning reports came from ordinary HTTP requests - no prompt injection, no model manipulation, and no "agent running wild."

The takeaways go beyond one sandbox. Agent payment systems have to enforce budgets, payee restrictions, approval workflows, and other controls before money moves. But those safeguards are only as reliable as the inputs and values they evaluate. If the rule checks one version of a payment and the credential uses another, it can look correct on paper - and still fail in practice.

This article breaks down the three reported bugs, what they suggest about payment security, and the practical checks teams should use when building (or evaluating) systems where software can request payments.

What happened in the $300 challenge

Pink built an MCP server and REST API that lets an AI agent request a payment. Before issuing a payment credential, the system checks the request against company-defined rules. Those can include budgets, amount thresholds, approved or blocked payees, operating hours, and required human approvals.

If the request passes, the system issues a single-use credential restricted to the specified payee and amount. That restriction matters: the agent shouldn't receive a general-purpose credential that could be redirected or reused for some other payment.

This challenge ran in a public sandbox using test money, not production funds. Pink opened the sandbox on October 7, then posted a $300 bounty the next day: $100 for each of the first three verified wins. The reports came in on Saturday, October 10, between 08:50 and 12:18 Pacific time. The full bounty was claimed before lunch.

What's striking is how the bugs were found. The reports used curl to hit the API directly. There was no need to trick a model, exploit prompt injection, or coax an agent into ignoring instructions. The failures lived in the system around the agent - how it parsed currency, how it picked time, and how it compared monetary values.

That distinction matters if you're evaluating "agentic" software. It's easy to obsess over model behavior because that's the most visible part. But once an agent can trigger real-world actions, the API, the policy engine, and the credential-issuance flow are part of the security boundary too.

Pink's public challenge repository includes the challenge details and fix log. The sandbox demo uses test money only. These reports should be read as findings from that environment - not proof that every agent payment product works the same way.

Three bugs, three broken assumptions

Each report pointed to a different mismatch between what a rule seemed to check and what the system actually processed. The rules themselves - an approval threshold, a time window, and an amount limit - weren't the real problem. The assumptions about input handling and evaluation were.

1. A trailing space changed the currency check

One rule required CFO approval for payments over $1,000. The reporter submitted 999 units with the currency set to EUR - note the trailing space.

The pricing engine didn't recognize the currency code. Instead of rejecting the request, it fell back to treating the amount as if it were in US dollars at a 1:1 rate. The policy therefore treated the payment as $999 and allowed it. At the sandbox's exchange rate, EUR 999 was about $1,079 - enough to cross the approval threshold.

The issue wasn't "no threshold." The issue was a default value being used for an invalid input. In finance, a fallback can make an app feel sturdy while quietly turning bad data into an approved transaction.

Pink updated the currency check to accept only an explicit allow-list of codes: USD, EUR, GBP, HKD, SGD, and JPY. Anything else now gets an HTTP 400 response. The system rejects the input instead of guessing.

2. A Singapore-time rule was evaluated against UTC

A second rule said requests outside 06:00 to 23:00 Singapore time should be routed to the finance on-call person. The server evaluated the rule using UTC instead of the workspace's configured timezone.

At 02:07 in Singapore, a $1,000 supplier reorder passed because the server-side UTC check decided it was within allowed hours. There was also another weakness: a payment request could include a local_hour value, and the hour used for evaluation could be chosen by the caller.

There's an important qualification in the original report. The specific reproduction used an agent for which the time rule didn't apply. Still, the underlying UTC-versus-workspace-time bug showed up in agents covered by the rule. The report's "edge case" matters - but so does the underlying problem the team verified.

Pink's fix was straightforward: every workspace gets a declared timezone, and time rules are evaluated against the server's clock in that timezone. The timezone is shown alongside the rules and in request traces. The local_hour field still exists for dry runs, but it can't control a real payment.

3. Rounding made the policy and credential disagree

The third report involved a rule that auto-approved payments of $500 or less. A payment for $500.0001 passed.

Here's what happened: the policy compared the amount after rounding it to the nearest cent. But the credential carried the unrounded amount. So the rule approved one value while the issued credential represented another. The difference was less than half a cent - but the comparison still wasn't consistent.

The reporter said the direct financial impact was tiny. Pink paid the bounty anyway, because the challenge's rule was about whether a credential was issued for an amount that the policy should have blocked, regardless of the size of the overage.

That approach is reasonable for a security challenge. If only huge losses get attention, researchers have a reason to ignore small discrepancies. But small discrepancies can still point to broader classes of defects - in this case, inconsistent handling of monetary precision.

Pink said amounts with more decimal places than the currency allows would be rejected with HTTP 400. In the source report, that fix was still in progress, so it should be treated as planned or ongoing - not as a fully completed control.

Issue What failed Reported response
Padded currency code Unknown currency silently received a 1:1 USD fallback Accept only listed currency codes; reject others with HTTP 400
Timezone mismatch A Singapore-time policy used the server's UTC clock; callers could also supply local_hour Set a timezone per workspace; use server time; restrict local_hour to dry runs
Precision mismatch The rule checked a rounded value while the credential carried an unrounded amount Reject excessive decimal precision; fix was still in progress in the report

How to build safer agent payment controls

These findings translate into a few practical engineering requirements. They're not specific to AI payments. They apply to any system where an API request is evaluated against a policy and then turned into an authorization, credential, or financial action.

Reject invalid input instead of inventing a meaning

For any value that affects a financial decision, use an allow-list.

  • For currencies: accept only supported codes in a canonical form.
  • For payees: resolve to a known payee record instead of relying on free-form names.
  • For amounts: validate both the numeric value and the precision the currency supports.

The rule is simple: unknown input should fail closed. A fallback might be okay for display formatting, but it's a bad choice when it changes the value being used to approve payment.

Do validation at the system boundary where the request enters. Normalizing inputs can help - trimming whitespace, for example - but it should be intentional and consistent. If one component trims and another doesn't, you'll get mismatches that look like "policy bugs" even when the policy is fine.

Use one authoritative clock

Time-based rules need a clear definition of "now." A policy written for Singapore time should not be evaluated against UTC unless the conversion is explicit and correct.

For real payments, the server should supply the time used for evaluation. A caller-controlled timestamp (or a local_hour-style field) shouldn't be able to make a request appear compliant.

Make the workspace timezone visible in configuration and traces. That helps enforcement and debugging. When a payment gets denied at 02:07 local time, operators should be able to see which timezone was used without reconstructing it from configuration.

Dry-run tools can accept simulated times because their job is to test policy behavior. The important line is that test inputs shouldn't sneak into live authorization decisions.

Evaluate and authorize the same normalized amount

Whatever the rule evaluates needs to be exactly what the credential authorizes. Don't compare a rounded value in one layer and then issue a credential from the raw input in another.

A safer flow is: parse the amount once, validate currency-specific precision, convert it into a canonical representation, and use that same representation for both policy evaluation and credential creation. For money arithmetic, teams should rely on representations built for exact currency values - rather than trusting floating-point behavior without a clear precision strategy.

Then test the boundaries directly. For a $500 threshold, include values like $499.99, $500.00, $500.01, and amounts with unsupported decimal places. For currency conversion or currency parsing, test malformed codes, whitespace, unsupported currencies, and values near approval thresholds.

Test the policy boundary, not just the happy path

A normal passing test for a $400 USD payment tells you little about what happens with malformed input - or at exactly the threshold.

Security testing should ask whether these three things line up:

  1. What the request contains.
  2. What the policy decision uses.
  3. What the resulting credential represents.

For each control, test at least:

  • The exact threshold and values immediately above and below it.
  • Missing, malformed, padded, or unsupported input.
  • Time boundaries in the workspace's configured timezone.
  • Attempts to supply values that should only come from the server.
  • Whether the issued credential matches the amount and payee that were evaluated.

This isn't a replacement for threat modeling across the system. It's a practical baseline that catches the mismatch category exposed by these reports.

The remaining risk: splitting payments

The bounty results didn't cover every gap. Pink also disclosed a remaining issue: an actor might split a larger payment into several smaller payments, each of which fits under an individual amount limit.

A per-payment rule answers: "Is this single transaction below the limit?" It doesn't necessarily answer: "How much has this payee, agent, or workspace already spent today?" That second question needs an aggregate control - like a daily limit or budget cap. If a workspace only has per-payment limits and no aggregate budget, splitting can still defeat the intended spending constraint.

Pink said its own benchmark tested this behavior and that Claude used the split-payment approach in 2 out of 80 runs. That's evidence from a limited benchmark, not a general measure of how often models will try it. Still, it shows why a payment system shouldn't rely on "the model will probably respect the spirit of the rule."

You need controls that consider cumulative activity. Depending on the product, that can mean limits per day, per payee, per agent, or across a workspace - alongside controls for concurrent requests and retries. What's "right" depends on the business workflow. A supplier might legitimately receive several invoices in one day; a blanket ban on repeated payments could break operations. The goal isn't to ban repetition - it's to make aggregate exposure explicit and enforceable.

This is also where the difference between a policy engine and a complete risk-control system matters. An amount threshold is one control. It isn't a substitute for budgets, approval escalation, idempotency, transaction monitoring, and clear operational ownership.

Conclusion

The $300 challenge surfaced three different implementation bugs. The shared lesson is simple: a payment rule is only as strong as the data and context it evaluates. An allow-list that falls back on unknown values, a timezone check evaluated against the wrong clock, or a rounded amount that doesn't match the issued credential can all undermine an otherwise reasonable rule.

If you're building agent payments, start at the transaction boundary: validate inputs strictly, keep authoritative values server-side, use a single canonical amount through evaluation and authorization, and set aggregate limits that match the real spending risk. Then test edge cases - not just the happy path.

The bounty is claimed, but the challenge remains open for findings and Hall of Fame credit. Review the challenge repository or try the test-money sandbox. Treat every rule as something you need to verify end to end - not just something you write down.

Frequently Asked Questions

No. The reported wins used direct HTTP requests, and none required prompt injection or model manipulation. The defects were in API and policy handling: currency validation, timezone selection, and amount precision.
No. The challenge used a public sandbox with test money. The reports describe vulnerabilities in that environment and do not establish that production funds were exposed.
Use strict input validation, server-controlled context for policy evaluation, and the same normalized amount and payee for approval and credential issuance. Aggregate budgets are also needed to address payment splitting.
The source report described payment splitting as a known, unresolved gap. Per-transaction limits may not stop several smaller payments from exceeding a total budget; an aggregate or daily limit is needed.

Related articles