AI Spending Security
When using the pay_invoice tool, you are authorizing an AI agent to spend Bitcoin on your behalf. This document covers the security considerations, mandatory safeguards, and best practices for safe AI-driven spending.
Understanding pay_invoice vs L402
| Aspect | L402 (access_l402_resource) | pay_invoice |
|---|---|---|
| Direction | You RECEIVE payments | You SPEND payments |
| Risk Level | Low (inbound funds) | High (outbound funds) |
| Use Case | Monetize your APIs | Pay for external APIs |
| Safeguards Needed | None (you're receiving) | Budget limits required |
Key Distinction: L402 is about receiving payments for your content/APIs. The pay_invoice tool is about spending your Bitcoin to pay for external services.
Risk Model
What Can Go Wrong
-
Unintended Payments
- AI misinterprets user intent and pays wrong invoice
- AI pays more than expected for a service
- AI makes multiple payments when one was intended
-
Malicious Prompt Injection
- Attacker injects payment request into AI context
- Phishing-style invoices embedded in content AI reads
- Social engineering via prompts
-
API Key Compromise
- Wallet API key (Strike, OpenNode, etc.) with withdrawal permissions stolen
- Key exposed in logs, environment dumps, or screenshots
- Key committed to version control
-
Budget Bypass Attempts
- Attackers try to make payments just under limit
- Multiple small payments to drain session budget
- Exploiting timing between budget check and payment
Mandatory Safeguards
1. Dedicated Spending Wallet
NEVER use your main wallet or business funds.
Create a dedicated wallet specifically for AI spending:
Main Wallet (Your Business)
├── Receives customer payments
├── Large balance
└── NEVER used for AI spending
AI Spending Wallet (Separate)
├── Only used by AI agents
├── Small balance (< $100 equivalent)
└── Easy to monitor and refill
Supported wallets:
- LND - Full L402 support (always returns preimage)
- NWC (CoinOS, CLINK) - Full L402 support (returns preimage)
- Strike - Full L402 support (returns preimage via
lightning.preImage) - NWC (Alby) - ✅ Works
- OpenNode - Direct payments only (no L402, no preimage)
- NWC (Primal) - Direct payments only (no preimage support yet)
L402 auto-pay requires preimage. These wallets work:
- LND - Guaranteed L402, self-hosted
- CoinOS (NWC) - Free, web-based
- CLINK (NWC) - Nostr-native
- Alby Hub (NWC) - You hold the keys, returns preimage
- Strike - Easy API key setup, returns preimage
OpenNode and Primal do NOT work for L402 - they don't return preimages.
Why: If something goes wrong, you only lose what's in the dedicated account.
2. Multi-Tier Approval System (AI-Proof)
Lightning Enable uses a USD-based multi-tier approval system that AI agents cannot bypass or modify. Configuration is stored in a file that only you can edit.
Configuration File Location
~/.lightning-enable/config.json
On Windows: C:\Users\YourName\.lightning-enable\config.json
Important: The MCP server exposes no tool to modify this file — the USD approval tiers here can only be changed by you editing it directly. (An agent can call budget with action="tighten" — formerly the standalone configure_budget tool — to tighten the separate runtime sats caps — lower them, never raise them — but it can never loosen any limit or touch this file. See below.) Caveat: an agent that has direct shell or filesystem access to the host could edit the file outside the server — see the threat-model note below.
Default Configuration
On first run, a default config file is created:
{
"currency": "USD",
"tiers": {
"autoApprove": 1.00,
"logAndApprove": 5.00,
"formConfirm": 25.00,
"urlConfirm": 100.00
},
"limits": {
"maxPerPayment": 500.00,
"maxPerSession": 100.00
},
"session": {
"cooldownSeconds": 2,
"requireApprovalForFirstPayment": false
},
"confirmation": {
"channel": "stderr"
}
}
Payments above the auto-approve threshold use out-of-band confirmation: the server delivers a code over the configured channel (stderr by default) for the human operator to relay (see Confirmation Channels below). The defaults provide a sensible balance between UX and safety; tighten them in your config if you want more payments to require confirmation.
Sats-Native Budgets
The USD tiers and limits above are converted to sats at call time from a live BTC price feed — and the whole system fails closed on any USD-denominated check if that feed is unavailable, rather than approving a payment it can't price. If you'd rather set a ceiling directly in sats — nothing to convert, nothing to fail closed on for that specific check — add the sats-native keys alongside or instead of the USD ones:
{
"limits": {
"maxPerPaymentSats": 50000,
"maxPerSessionSats": 200000
},
"tiers": {
"autoApproveSats": 1000
}
}
Whichever pair is present for a given check governs that check — the two aren't summed. A sats-native limit is immune to a price-feed outage by construction; any USD-denominated tier you still have configured stays fail-closed as before. budget(action="tighten") accepts maxPerPaymentSats / maxPerSessionSats for runtime tightening regardless of which pair the operator configured in the file.
How Each Tier Works
| Amount (USD) | Tier | Behavior |
|---|---|---|
| ≤ $1.00 | Auto-Approve | Payment proceeds without any notification |
| $1.00 - $5.00 | Log & Approve | Payment proceeds, logged for your review |
| $5.00 - $25.00 | Form Confirm | Requires confirmation (see below) |
| $25.00 - $100.00 | URL Confirm | Requires explicit confirmation with amount verification |
| $100.00 - $500.00 | Out-of-band code | Requires the console confirmation code (see below) |
| > $500.00 | Deny | Payment blocked entirely (maxPerPayment) |
Out-of-Band Confirmation (.NET v1.12.10+, Python v1.12.12+)
When a payment exceeds the auto-approve threshold, the server delivers a confirmation code over a channel the human operator sees, not the AI. The code is never returned in a tool result, so a prompt-injected agent can't read its own code and self-approve.
How confirmation works:
- The agent calls
pay_invoice(oraccess_l402_resource/pay_l402_challenge) for an amount above the auto-approve threshold. - The tool returns a response telling the agent that confirmation is required — but without the code.
- The server delivers the confirmation code over the configured channel (see Confirmation Channels below), where you (the human) can read it.
- You give the code to the AI, which re-calls the original tool with its confirmation-nonce parameter (
confirmationNoncein .NET,confirmation_noncein Python) to proceed. The separateverify_confirmation_codetool (renamed fromconfirm_paymentin v1.17.0) can verify a code (it echoes the amount/tool it authorizes) but does not execute the payment — the original tool does.
The confirmation code is bound to the exact amount, the exact tool, AND the exact destination (the invoice, URL, or on-chain address) it approved — it can't be reused for a different amount or tool, and it can't be redirected to a different invoice or address (destination binding added in v1.12.13).
wallet_ops with action="send_onchain" (formerly the standalone send_onchain tool) always requires this confirmation, even for small amounts, because on-chain payments are irreversible. It also fails closed if the budget service is unavailable.
This ensures you keep control over payments above your auto-approve threshold — the AI can't approve its own payment, because the approving code reaches only the channel you configured, which the human (or a system acting for them) reads.
Confirmation Channels
confirmation.channel in the config file picks where the code goes:
confirmation.channel | Where the code goes | Use it when |
|---|---|---|
stderr (default) | The server's console/stderr — the assumption the rest of this page's threat model rests on | Interactive local use: Claude Code, Claude Desktop, a terminal session |
refuse | Nowhere — the payment is refused outright instead of printed anywhere | Non-interactive/hosted contexts, where nothing is reliably watching stderr and printing a code there risks it being readable by the wrong process instead of a human |
webhook | POSTed to a webhook URL you configure | A human is notified out-of-band (chat app, pager) instead of watching a terminal |
file | Written to a local file the human reads on their own schedule | Headless or scheduled runs with no one watching in real time |
LIGHTNING_ENABLE_HOSTED=1 changes the default rather than adding a fifth channel: when set, and the process is not attached to a TTY, the effective channel becomes refuse instead of stderr. This exists because the stderr channel's safety depends entirely on a human — not the agent, not a co-located process — being the one who reads the server's console (see the threat-model box below); a hosted, non-interactive deployment can't assume that, so it refuses rather than guess. Set confirmation.channel explicitly to webhook or file if you want a hosted deployment to still deliver codes somewhere instead of refusing.
Out-of-band confirmation rests on one assumption: the AI runtime cannot read the MCP server's console / stderr or centralized logs, and cannot run shell commands to read them. That holds for the common setup (Claude Desktop / IDE launches the server as a subprocess whose stderr the model never sees). It does not hold if the agent shares a shell or host with the server and can cat the logs or read the process's stderr — there it could read its own confirmation code, and could also edit ~/.lightning-enable/config.json directly. For agents with local shell/filesystem access, run the MCP server somewhere the agent can't read its stderr or files (a separate user/host/container).
Why This Is AI-Proof
- File-Based Configuration - The config lives in your home directory, not in environment variables that could be exposed
- Tighten-Only Runtime Changes - The
budgettool (action="tighten") can only lower the per-request / per-session caps at runtime; an agent can never raise them above the limits in your config file - Out-of-Band Confirmation - For payments above the auto-approve threshold, the confirmation code is delivered over the configured channel (the human sees it, or in a hosted context the payment is refused by default — see Confirmation Channels) and is never returned to the AI, so the AI can't approve its own payments
- First Payment Protection (opt-in) - When
requireApprovalForFirstPaymentis enabled (it isfalseby default, as shown above), the first payment of each session requires explicit confirmation - Cooldown Periods - Prevents rapid-fire payment attacks
Customizing Your Limits
Edit ~/.lightning-enable/config.json:
{
"currency": "USD",
"tiers": {
"autoApprove": 0.50, // More restrictive: only auto-approve $0.50
"logAndApprove": 2.00, // Log anything above $2.00
"formConfirm": 10.00, // Require confirmation above $10
"urlConfirm": 50.00 // Require amount verification above $50
},
"limits": {
"maxPerPayment": 100.00, // Never pay more than $100 in one payment
"maxPerSession": 50.00 // Never spend more than $50 per session
},
"session": {
"cooldownSeconds": 2,
"requireApprovalForFirstPayment": false
}
}
If you want to require confirmation for ALL payments, set autoApprove to 0. Every payment will then require out-of-band confirmation — the server delivers a code over your configured channel and you relay it to the AI to approve each one.
Checking Your Budget Status
Use the budget tool with action="status" (read-only; formerly the standalone get_budget_status tool) to see current configuration:
budget action="status"
Response:
{
"success": true,
"message": "Budget configuration is READ-ONLY. Edit ~/.lightning-enable/config.json to change limits.",
"tiers": {
"autoApproveUsd": 0.10,
"formConfirmUsd": 10.00,
...
},
"session": {
"spentUsd": 0.45,
"remainingUsd": 99.55
},
"security": {
"aiCanModify": false,
"howToChange": "Edit the config.json file directly. AI agents cannot modify budget limits."
}
}
The L402_MAX_SATS_PER_REQUEST and L402_MAX_SATS_PER_SESSION environment variables have been removed from both packages — setting them has no effect. Operator limits live in ~/.lightning-enable/config.json (limits.maxPerPayment / limits.maxPerSession in USD, or limits.maxPerPaymentSats / limits.maxPerSessionSats — see Sats-Native Budgets). The runtime sats caps can only be tightened via the budget tool's action="tighten" (per_request / per_session in Python, perRequest / perSession in .NET, or maxPerPaymentSats / maxPerSessionSats) — never raised.
3. API Key Permissions
When creating your wallet API key (Strike, OpenNode, etc.) for AI spending:
- Use withdrawal-only permissions if available
- Create a dedicated key - don't reuse keys
- Rotate keys regularly (monthly recommended)
- Never commit keys to version control
- Never share keys in screenshots or logs
# MCP Configuration - store key securely (use your wallet of choice)
export STRIKE_API_KEY="your-strike-key-here"
# or
export OPENNODE_API_KEY="your-opennode-key-here"
export OPENNODE_ENVIRONMENT="production"
4. Monitoring and Auditing
Review payments after every AI session:
# Use the MCP tool to check payment history
get_payment_history limit=20
Set up monitoring:
- Enable email notifications for withdrawals in your wallet provider (Strike, OpenNode, etc.)
- Review weekly spending totals
- Set up balance alerts (notify when balance drops below X)
- Check for unexpected payment patterns
Environment Variable Checklist
Before using pay_invoice, verify one of the following wallet options is configured:
Strike (recommended):
| Variable | Purpose | Recommended Value |
|---|---|---|
STRIKE_API_KEY | API authentication | API key from dedicated Strike account |
OpenNode (alternative, direct payments only — no L402):
| Variable | Purpose | Recommended Value |
|---|---|---|
OPENNODE_API_KEY | API authentication | Withdrawal-only key from dedicated account |
OPENNODE_ENVIRONMENT | Network selection | production for mainnet |
NWC (for L402 auto-pay):
| Variable | Purpose | Recommended Value |
|---|---|---|
NWC_CONNECTION_STRING | Wallet connection | Connection string from CoinOS, CLINK, or Alby Hub |
LND (for L402 auto-pay, self-hosted):
| Variable | Purpose | Recommended Value |
|---|---|---|
LND_REST_HOST | LND REST API host | localhost:8080 |
LND_MACAROON_HEX | Admin macaroon | Hex-encoded admin macaroon |
Spending limits: there are no budget environment variables — the legacy L402_MAX_SATS_PER_REQUEST / L402_MAX_SATS_PER_SESSION vars were removed. Configure limits in ~/.lightning-enable/config.json before first use; agents can tighten (only) the runtime sats caps with budget(action="tighten").
What pay_invoice Should NOT Be Used For
Do NOT use pay_invoice if:
- Your wallet balance exceeds
100,000 sats ($100 at 100k sats/$) - You're using production/business funds
- The AI will run unattended for long periods
- You haven't configured budget limits
- You're using a shared or untrusted MCP configuration
- You haven't reviewed the AI agent's capabilities
Recommended for:
- Developer testing and prototyping
- Light interactive use with supervision
- Small, bounded research tasks
- Learning and experimentation
Architecture: We Never Touch Funds
Lightning Enable is API middleware — your payment provider facilitates custody and settlement:
Your Wallet (Strike, LND, NWC, etc.) Lightning Enable MCP Recipient
│ │ │
│ ◄────────── You control ──────────► │ ◄─── Software ───► │
│ │ │
▼ ▼ ▼
Provider custody Just a tool Receives
Your account API middleware payment
Your responsibility No access to funds
Key points:
- Lightning Enable does not hold funds — the payment provider facilitates custody and settlement
- You provide your own provider API key (BYOA model — Strike, LND, NWC, or OpenNode)
- Your payment provider holds your funds (they are licensed and regulated)
- We never see, touch, or control your Bitcoin
- All compliance/KYB/KYC is between you and your provider
Incident Response
If you suspect unauthorized payments:
- Immediately rotate your wallet API key (Strike, OpenNode, etc.)
- Check payment history in your wallet provider's dashboard
- Review what invoices were paid
- Update MCP configuration with new key
- Lower budget limits
- Investigate how the key may have been compromised
Security Recommendations Summary
| Priority | Action |
|---|---|
| Critical | Use dedicated wallet with limited funds |
| Critical | Configure budget limits BEFORE first use |
| High | Use withdrawal-only API key |
| High | Review payment history after sessions |
| Medium | Rotate API keys monthly |
| Medium | Set up balance alerts |
| Low | Log and audit all AI sessions |
Related Documentation
- Spending Guidelines - Recommended budgets by use case
- Legal Considerations - Liability and terms
- AI Agent Integration - MCP setup guide