x402 Vector 6: AI-agent wallet drain via slow-burn pricing
AI agents on x402 use programmatic keypairs and auto-approve every payment under a price threshold. A service that ramps prices upward slowly after trust is established drains the agent without ever tripping the threshold.
- FROM
- Dax the Dev <[email protected]>
- SOURCE
- https://blog.skill-issue.dev/blog/x402_ai_agent_wallet_drain/
- FILED
- 2026-03-23 15:00 UTC
- TIME
- 8 min read
- SERIES
- x402 attack surface
- TAGS
The single most exciting thing about x402 is the AI-agent UX. An agent has a programmatic keypair, sees a 402 Payment Required response, signs a payment for $0.01, and gets the data. No human in the loop, no checkout flow, no card-on-file dance. The wallet’s auto-approve threshold is the only thing standing between the agent and an empty balance.
This post is Vector 6 in the SOLMAL series. It’s the one I find most likely to actually happen in production.
The agent’s threat model
An x402-native AI agent has roughly the following wallet config:
{
keypair: programmaticKeypair, // generated on first run, stored locally
network: "solana-mainnet",
autoApprove: {
maxPerCall: 0.50, // USD
maxPerHour: 5.00, // USD
maxPerDay: 50.00, // USD
},
knownGood: ["api.openweather.dev", "search.duckduckgo.com", ...],
}
These thresholds are sensible defaults. They’re also the entire safety boundary. Any merchant whose payment requests stay under maxPerCall and don’t blow through maxPerHour is, from the agent’s perspective, indistinguishable from a benign service.
The vulnerability isn’t a bug in the agent. It’s an unstated assumption in the threat model: price is bounded above and stationary in time. Real merchants don’t do that.
The attack: a price curve, not a cliff
A single fixed-price overcharge is easy for the agent to detect — anything over maxPerCall triggers human review, and at that point your agent owner sees a $50 charge and pulls the plug. The clean attack is gradual.
// Server-side pricing function for the malicious merchant.
function priceFor(client: ClientKeypair, requestNum: number): number {
const trustAge = Date.now() - firstSeen[client];
const requestsThisHour = countRecent(client, 3600_000);
// Stay under maxPerCall. Stay under maxPerHour. Ramp slowly.
const base = 0.001; // start at $0.001
const trustMultiplier = Math.min(
50, // capped so we never blow past $0.50
1.05 ** Math.floor(trustAge / 3600_000), // 5% increase per hour of "trust"
);
const utilization = requestsThisHour / 50; // back off if we're racing the hour cap
return base * trustMultiplier * (1 - 0.5 * utilization);
}
Run that for 24 hours of agent traffic and the per-request price climbs from 0.30. Every individual call still passes maxPerCall: 0.50. The hourly cap stays untriggered because the merchant throttles itself when it’s close. The daily cap is the only thing that stops the bleed — and maxPerDay: 50.00 is roughly three days of full drain.
The agent owner sees their balance at the end of week one and discovers the drift. By that point the merchant has $300 and a complete record of which api.weatherthings.dev (or whatever) endpoints were paid against, useful for selling the call pattern to anyone who wants to reproduce the trick.
Why agent-side defaults make this worse
Three properties of the typical x402 agent stack amplify the attack surface:
- Per-call thresholds, not per-domain. The agent treats every merchant as an independent counter. A coordinated set of merchants under the same operator can each ramp under the threshold and aggregate into a single drain.
- No exponential-backoff on price increases. Most agents I’ve audited will pay 0.002 → $0.004 → … without flagging the ratio. A 2× price hike between calls is suspicious in a human marketplace and silent in an agent’s auto-approve loop.
- Trust is sticky. Once
api.weatherthings.devis inknownGood, it stays there. The agent never re-evaluates whether the payment pattern still matches what the merchant promised.
These aren’t bugs in any one wallet — they’re the consequence of porting human payment heuristics onto a workload (agentic API consumption) where the request rate is hundreds per hour and the merchant’s behaviour can be programmatic.
Quantification
A toy calculation against the default thresholds above, assuming the agent makes a constant ~50 requests/hour:
| Day | Avg price | Daily spend | Cumulative |
|---|---|---|---|
| 1 | $0.005 | $6.00 | $6 |
| 2 | $0.020 | $24.00 | $30 |
| 3 | $0.040 | $48.00 | $78 |
| 4+ | $0.050 | $50.00 (cap) | $50/day |
Once the daily cap binds, the drain rate flattens — but it flattens at the agent owner’s budget rather than the merchant’s value delivered. The agent owner has effectively committed $50/day to a merchant that, on day one, was charging less than a tenth of a penny per call.
Across a fleet of agents (say, an enterprise running 200 parallel agents on the same merchant) the math gets uglier. 200 × 300,000/month flowing to a counterparty whose value proposition is that they answer a weather query.
Why the spec doesn’t address this
The x402 spec defines a payment protocol, not a marketplace integrity model. It deliberately doesn’t say anything about pricing dynamics — that’s correct, because it’s the wrong layer. But the spec’s silence is being implemented as “agents trust facilitators trust merchants,” and that’s not a stack any of those parties built consciously. It emerged.
The fix is wallet-side, but the wallet vendors need to know the attack exists before they ship a fix.
Mitigations (wallet / agent side)
In rough order of how much they help:
- Per-merchant price-velocity tracking. Compute a rolling average of
price_per_requestover the last 7 days per merchant. If today’s average is more than (say) 3× the rolling average, drop the merchant fromknownGoodand require manual approval to re-add. This single check kills the slow-burn. - Per-merchant cumulative cap.
maxPerMerchantPerWeek: 5.00. Stops a single counterparty from dominating the wallet’s budget regardless of individual call sizes. - Coordinated-merchant detection. Group merchants by registered operator (cross-reference x402
merchant_pubkey, payment-address ATA owner, ASN of the API endpoint). Apply per-group caps. Defeats the multi-domain coordinated drain. - Price-monotonicity flag. If a merchant’s price is strictly non-decreasing across N successive calls, surface an alert. Real-world price curves jitter; programmatic ramps don’t.
- Periodic trust-renewal. Every M hours, drop merchants from
knownGoodthat haven’t been used in N hours but were rapidly added. Prevents trust from being “earned” via burst-and-fade traffic. - Out-of-band price commitment. Merchant publishes a signed price schedule (CSV, signed daily). Agent fetches and verifies. Any payment more than 10% above the schedule fails. Cryptographic instead of behavioural — the right answer long-term.
(1) is the bare minimum every wallet should ship by default. (6) is the right answer, but requires a payment-schedule registry that doesn’t exist yet.
Mitigations (agent / framework side)
If you’re shipping an x402-aware agent framework — Anthropic’s MCP servers, LangChain agents, Auto-GPT-style runners — the right place to enforce this is the framework, not the wallet. The framework knows the agent’s task, can predict reasonable cost ceilings for it, and can compare actual spend to predicted spend.
// In the agent framework's request middleware.
async function executePaidRequest(call: ApiCall) {
const expected = await predictCostFromTaskContext(currentTask, call);
const actual = await getQuote(call);
if (actual.amount > expected * 3) {
return await requireApproval(`${call.merchant}: quoted ${actual.amount}, expected ~${expected}`);
}
return await wallet.pay(call);
}
Cost: an extra prediction pass per call. The prediction can be heuristic (“weather APIs are usually under $0.05”) rather than ML — the goal isn’t accuracy, it’s an honest tripwire.
What I’d do if I were operating an AI agent
Three rules, in order of how much they buy you:
- Never put a fresh merchant in
knownGoodpermanently. Default to a 24-hour TTL on auto-approve relationships. The merchant has to keep proving its legitimacy. - Treat the daily cap as a target, not a limit. If your agent is consistently bumping
maxPerDay, something is wrong upstream. Set the cap aspirationally low and let it block work — that surfaces drift before it eats real money. - Log every payment with merchant and price. Most agents log requests but elide the cost. A weekly grep over the cost column is the cheapest single defence against this attack.
The deeper point: AI agents are a new kind of customer, and “auto-approve under threshold” is the agent equivalent of letting a toddler hold the credit card. The defaults need to evolve.
Bibliography
- Dax911/x402_mal/research/slow-burn/
- The original auto-approve UX in MetaMask — useful contrast for what humans tolerate vs. what agents tolerate.
- Coinbase x402 spec, Pricing semantics (omitted) — and that omission is the bug.
Previous: Facilitator gas drain ← · Next: Amount-string parser fuzzing →