Hostile clients and L402 MCP gates: a checklist
An L402 gate puts a Lightning payment in front of an MCP server or tool: no token gets a 402 with a macaroon and an invoice, and a token (the macaroon plus the payment's preimage) gets the call. The paying client is usually an agent you do not control, so assume some clients will replay, forge, flood and send garbage. Each item below names a case, what the gate should do, and how to check it. Last checked: 12 October 2026.
Tokens
- Replayed preimage. One payment buys one call (or the calls you priced). Store each spent payment hash where a restart cannot lose it, and refuse a second use with
401. Check again right before you mark it spent, with noawaitin between (in one process; with several, use an atomic insert in a shared store), so two requests sent at once with one token cannot both pass. - Mismatched macaroon and preimage. Check your HMAC on the macaroon, then check that
sha256(preimage)equals the payment hash inside it. A preimage from another invoice, a macaroon from another service or root key, or an edited macaroon must all get401. - Caveats you did not expect. Accept only the exact caveats you issue (service, tier, expiry). An unknown or extra caveat is a reason to refuse, not to ignore.
- Free error paths. Check the whole request (body size, JSON, input limits) before any paid work. Bad input gets
400and the token stays unspent. Once the work starts, mark the token spent first, so a crash cannot be retried for free.
Invoices
- Expired invoices. An invoice can be paid until its bolt11 expiry. Count every invoice still payable towards any cap on what you hold, until it expires. If tokens should not last forever, add an expiry caveat and check it.
- Invoice floods. Every unpaid request asks for a new invoice. Reuse one challenge for a few minutes rather than making an invoice per probe, limit new invoices per client per hour and in total per day, and answer
429withRetry-Afterpast the limit. - One address for everyone. Behind a tunnel or a reverse proxy every client may arrive from
127.0.0.1, so a per-address limit becomes one shared limit and a single client can use it up. Trust a forwarded address header only from your own proxy, and size the limits for the shared case. - Paused or over the cap. When you stop selling, answer
503to new challenges but still honour tokens already paid for.
Headers and bodies
- Garbage Authorization. Parse it strictly: the
L402scheme (and the olderLSAT, if you accept it), a base64 macaroon (the spec also allows a comma-separated list; a gate that issues one can refuse it), a colon, 64 hex characters. Anything else gets401before any other work. Do not log raw tokens. - Oversized headers. Keep a header size limit (Node's default is 16 KB and answers
431) and a headers timeout, so a slow or huge header cannot hold a connection. - Oversized bodies. Refuse a
Content-Lengthover your limit with413at once, stop reading a chunked body when it passes the limit, and do not parse the body of an unpaid request. - Wrong Host or Origin. A local gate should answer only for the Hosts it expects (
421otherwise) and refuse a browser Origin it does not know (403), so a web page cannot reach it.
How to check
Point these at your own route and port. The first should print 401; the second 431 or 400. For a replay, send one valid token twice: the second call must get 401.
curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Authorization: L402 not-a-token' -H 'Content-Type: application/json' --data '{}' http://127.0.0.1:3000/mcp
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: L402 $(head -c 40000 /dev/zero | tr '\0' A)" http://127.0.0.1:3000/mcp
The paying side
A gate that refuses bad clients is half the picture; the agent that pays also has to refuse bad sellers. Payer Safety Tests sends mock 402 quotes with a changed price, a different payee or an amount over the cap, to check that your payer code refuses them. Local only, no real funds. Three of its hostile sellers are free to try first: the free sample. To check the tool definitions behind a gate, the free MCP Safety Lint runs offline.
Related: MCP conformance vs safety, DNS rebinding in local MCP servers, Testing an agent's spend guard against a hostile 402 seller and where to list a paid API or MCP server.