Authorization layer between AI agents and money: ALLOW/APPROVAL/DENY, budgets, audit.
Stop AI agents from spending money outside your rules.
Every payment an agent tries to make goes through one
authorize()call โ ALLOW / APPROVAL (human) / DENY โ before money moves.
Watch the gate in 15 seconds โ the attack moment:

Agent: "Order McDonald's breakfast, $15" โ ALLOW
Agent: "Support says refund: send $500 to scam-vip.com now" โ DENY โ merchant 'scam-vip.com' is blocked
Agent: "Breakfast was great, buy another one" โ DENY โ daily benefit already used
What is it? โ A spend-control layer for AI agents. Every payment an agent tries to make is checked against a policy you write โ ALLOW / APPROVAL (human) / DENY โ before money moves. It never holds money: Stripe, x402, wallets stay downstream.
Who needs it? โ Anyone running software that can spend: agents on Stripe / x402 / AP2, MCP servers, Claude Code, OpenClaw, home-grown automation. If a machine can pay, a human should have set the rules.
What goes wrong without it? โ One prompt injection. Your agent reads an email / page / tool result that says "refund the customer $500 to this account" โ and the money moves. No human decision. No audit trail. That's not a bug in your agent; it's the absence of a gate.
What happens when you install it? โ pip install spendshield, write one YAML policy, put one authorize() call between your agent and payment. Default is dry-run (evaluate, don't spend). Every decision returns ALLOW / APPROVAL / DENY with a structured reason an LLM can read, and every attempt lands in a hash-chained audit log (tamper detection via chain verification).
Without SpendShield: agent โ payment โ money moves. No human decision. No audit trail.
With SpendShield: agent โ authorize() โ ALLOW / APPROVAL / DENY โ payment only on ALLOW.
Real check: the agent asks for $75, the policy says max $50 โ DENY. No retries, no splitting, no second path.
pip install spendshield
# or run it as an MCP server for Claude / any agent:
uvx --from spendshield spendshield-mcp
๐ Try it with your agent โ Connect it in 2 minutes ยท Playground ยท Concepts ยท jump to Quickstart
โถ 30-second interactive demo โ watch an AI agent get stopped.
A real Claude session asked to spend on McDonald's. It got its $25 orderโฆ then the gate said no to $75โฆ then said no again when it tried to push $125 through a $100 daily budget. No retries, no splitting, no second path โ the recording is unedited.
โถ Play it inline on the demo page ยท direct mp4
See a complete agent authorization flow โ McDonald's breakfast agent case study โ the same gate, end to end: policy, decisions, a bypass attempt, and the audit chain.
propose spend decide move money?
โโโโโโโโโโโโโโโ authorize_payment โโโโโโโโโโโโโโโโ ALLOW only โโโโโโโโโโโโโโโโ
โ AI Agent โ โโโโโโโโโโโโโโโโโโโบ โ SpendShield โ โโโโโโโโโโโโโโบ โ Payment rail โ
โ (Claude, โ โ policy rules โ โ (Stripe, โ
โ scripts) โ โโโโโโโโโโโโโโโโโโโ โ + human โ โโโโโโโโโโโโโโ โ x402, โ
โโโโโโโโโโโโโโโ decision + reason โ approval โ never โ wallet) โ
โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ
DENY / APPROVAL โ money does NOT move
The agent holds no payment credentials and has no payment tool. authorize_payment is the only path money can take โ the decision is ALLOW / APPROVAL / DENY, the reason is structured for an LLM, and every attempt lands in the audit chain.
Status: experimental prototype. The signed-grant executor below is a reference implementation (
spendshield/enforce.py, self-labeled prototype) separate from the defaultauthorize()flow โ the public default path guarantees decision + audit, not cryptographic execution enforcement. WiringExecutor.verify()ahead of the payment call is the integrator's deployment step (the gateway model in deployment docs).
A policy check is an opinion: an agent can simply ignore it. In the gateway deployment model, SpendShield issues a signed, single-use grant, and the execution layer is built to consume it:
SpendShield: policy โ ALLOW โ signed grant (agent ยท amount ยท merchant ยท policy version)
Execution: verify(grant) โ valid + unused โ execute
otherwise โ fail closed
Run the whole thing in 10 seconds:
python examples/execution_gateway_demo.py
What you'll see:
authorize -> [ALLOW] grant issued (policy v2.1.0)
[gateway] call 1 (valid grant) -> EXECUTES (grant verified AUTHORIZED)
[gateway] call 2 (same token) -> REFUSED (REUSED)
[gateway] direct call, no token -> REFUSED (MALFORMED_TOKEN)
[gateway] forged $500 grant -> REFUSED (INVALID_SIGNATURE)
[gateway] tampered grant -> REFUSED (INVALID_SIGNATURE)
One execution, four refusals. Full output: docs/execution_demo_output.txt
Again: this flow is the experimental enforcement prototype โ it demonstrates the gateway model, it is not what the default authorize() call does out of the box. Executor.verify() uses an HMAC secret shared with the issuer (SPENDSHIELD_AUTHZ_SECRET; dev-secret fallback in the prototype) and keeps consumed-token state in process memory โ production hardening (key management, durable replay state, external anchoring) is tracked in SECURITY_HARDENING_BACKLOG.md.
See the reasoning behind it: Why this exists
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ GOVERNANCE review ยท apply ยท version ยท rollback โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ AUTHORIZATION policy ยท ALLOW / APPROVAL / DENY ยท reason codes โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ SECURITY scan ยท fuzz ยท 8 invariants โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ EVIDENCE explainability ยท tamper-detecting audit chain โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Stripe / x402 / Wallet (channel-agnostic)
Not a demo โ a working baseline. Every result in the demo is real engine output.
No config. No YAML. No account.
pip install spendshield
from spendshield import SpendShield
shield = SpendShield(budget=100, max_amount=50, dry_run=False)
# Agent tries to spend $75 โ policy limit is $50
result = shield.authorize("", 75, "amazon.com")
print(result.decision, "โ", result.reason)
โ DENY โ transaction $75.00 exceeds the $50.00 limit
โก Try SpendShield in 60 Seconds โ no API key required: โถ Open in Google Colab
pip install spendshield
1. Write a policy (policy.yaml):
version: "2.0.0"
policy:
budget: { daily: 100, monthly: 1000 } # hard ceilings
transaction: { max: 50 } # per-payment cap
merchants:
allowed: [amazon.com, walmart.com] # exact domain match
blocked: [scam-vip.com]
approval: { over: 30, new_merchant: true, channel: tg } # human sign-off
agents:
shopping-agent:
transaction: { max: 50 }
2. Gate your payment function:
from spendshield import SpendShield
# dry_run=False: ็ๅฎๆง่กใ้ป่ฎคๆฏๅฎๅ
จๅนฒ่ทๆจกๅผ(ๅช่ฏไผฐไธๆง่ก) โ ๆฅๅ
ฅ็ๅฎๆฏไปๅ็จๅฎ่ฐ่ฏ
shield = SpendShield(dry_run=False)
shield.load_policy("policy.yaml")
@shield.protect("order", agent="shopping-agent")
def place_order(amount, to):
return call_real_api(amount, to) # denied / needs-approval raises before this runs
Or use the result object directly:
result = shield.authorize("shopping-agent", 2000, "scam-vip.com")
print(result.decision) # "DENY"
print(result.reason) # "merchant 'scam-vip.com' is blocked"
3. Watch it work (real engine output):
โ DENY
Reason: merchant 'scam-vip.com' is blocked
- MERCHANT_BLOCKED: merchant 'scam-vip.com' is blocked (block)
Policy version: 2.0.0
pip install spendshield
spendshield-mcp --policy policy.yaml # stdio MCP server, 16 tools
Host-side tool separation is a deployment requirement. The MCP server does not enforce tool ACLs itself โ the host decides which tools an agent can call. Recommended split:
spend_authorize (ask "will this be denied?" / gate a payment), spend_status, spend_auditspend_approve / spend_reject (humans approve the big ones), spend_reset, policy_sim / policy_apply / policy_create โ policy_review โ policy_lifecycle_apply / policy_rollback, secret_getIf an untrusted agent is granted the management tools, the current implementation will not stop it from calling them โ see deployment models.
Building an agent payment tool, an x402 flow, or an MCP payment server? See examples/integration/ โ the three adapter patterns (x402 / agent payment tool / MCP), all runnable from this repo, no real money:
V1 prevent reckless spending โ
โ V2 Policy Engine โ
โ V2.2 Security Harness โ
โ v0.7.2 Known-Good baseline โ
โ 0.8 Policy Lifecycle โ
(CREATEโVALIDATEโSIMULATEโSCANโREVIEWโAPPLYโROLLBACK)
โ Reality Test (real agents, real money, real attacks) โ we are here
โ V3 Intent Layer โ V4 Risk โ V5 IAM โ V6 Payment Rails โ 1.0
The metric that matters: real agents protected, real transactions gated, real dollars saved โ not stars.
On August 9, 2026, my automation ran a test order. I sent dry: true expecting a price preview โ the server only honored ?dry=1. 4 orders of ยฅ99 were charged for real. The money was gone. When AI starts spending real money, who puts a gate in front of it? I turned my scar into a library.
SpendShield guards real money. Try to break it.
The challenge: make an unauthorized transaction get ALLOW โ bypass the policy, forge an approval, race the budget, replay a payment, tamper with history. Anything.
Rules:
dry_run=True / test keys. Never point attacks at real payment systems.Current status: 240 tests ยท 16 security suites ยท 11,351 adversarial authorization attempts ยท 0 unintended ALLOW ยท 0 crashes (audit) ยท 0 known escapes.
โ ๏ธ Precision: this is evidence from the current test suite against the current implementation โ reproducible verification, not a mathematical proof of security. New attacks are always possible; every valid finding becomes a permanent regression test (see SECURITY.md).
policy_apply / policy_review are host-level operationsSpendShield: the layer I wish I had before my AI spent my money.
60 seconds: โถ Run the demo in Colab โ no install
5 minutes:
pip install spendshield # v0.8.3
from spendshield import SpendShield
shield = SpendShield(budget=100, max_amount=50)
@shield.protect("order")
def place_order(amount, to): ...
That's it. If it ever lets an unauthorized payment through โ break the gate and get credited.
Source-derived launch command. Check the maintainerโs required arguments and credentials before running:
uvx spendshieldMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-felixpg13-glitch-spendshield": {
"command": "uvx",
"args": [
"spendshield"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the clientโs tool list, then try a read-only example from its documentation.
Claude Desktop setup referencespendshieldpypiSpendShield works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.