# Security Model > How Lightning Agent Tools isolates private keys, scopes credentials, and > controls what agents can do. Agents that handle real bitcoin need a security model built for autonomous operation. The core principle is straightforward: give the agent the minimum credentials required for its task and keep private keys on a separate machine. The kit enforces this through three tiers of access, each with different trust assumptions and failure modes. ## Three Tiers of Access ```mermaid graph LR subgraph Tier1["Tier 1: Watch-Only + Remote Signer"] direction TB A1["Agent machine"] S1["Signer machine"] A1 --- |"gRPC/TLS"| S1 A1 -.- N1["No private keys"] S1 -.- K1["Holds all keys"] end subgraph Tier2["Tier 2: Standalone"] direction TB A2["Agent machine"] A2 -.- K2["Keys on disk
(0600 permissions)"] end subgraph Tier3["Tier 3: Read-Only via MCP-LNC"] direction TB A3["Agent machine"] MB["Mailbox relay"] A3 --- |"encrypted WebSocket"| MB A3 -.- N3["No credentials on disk
Ephemeral keys only"] end ``` ### Tier 1: Watch-Only with Remote Signer This is the default and recommended configuration. The agent machine runs an lnd node in watch-only mode. It can see balances, manage channels, and route payments, but it has no private keys. All signing is delegated to a separate signer node over an authenticated gRPC connection. **What the agent machine has:** - Account xpubs (public keys for address derivation) - A TLS certificate and macaroon for the signer's gRPC interface - Scoped macaroons for the local lnd (baked via `macaroon-bakery`) **What the agent machine does not have:** - The wallet seed - Any private keys (funding, revocation, HTLC, or on-chain) **If the agent machine is compromised,** an attacker can observe channel state, balances, and payment history. They can see which peers the node is connected to and the topology of its channels. But they cannot sign transactions, sweep funds, or forge channel commitment updates. The keys are simply not there. **If the signer machine is compromised,** the attacker has full control over all private keys and can sign arbitrary transactions. This is a complete compromise. The signer machine should have a minimal attack surface: no public-facing services, restricted network access (only the watch-only node should reach port 10012), and ideally dedicated hardware. **Setup:** Use the `lightning-security-module` skill on the signer machine and the `lnd` skill in watch-only mode on the agent machine. See [Architecture](architecture.md#remote-signer) for the signing flow. ### Tier 2: Standalone The node generates its own seed and stores it locally. The 24-word mnemonic is written to `~/.lnget/lnd/seed.txt` and the wallet passphrase to `~/.lnget/lnd/wallet-password.txt`, both with mode 0600. This mode is appropriate for: - Testnet and regtest development - Small-value experiments on mainnet - Environments where a separate signer machine is impractical It is **not appropriate** for production deployments with significant funds. Anyone with read access to `~/.lnget/lnd/seed.txt` can reconstruct the wallet's private keys. **Setup:** Pass `--mode standalone` to `create-wallet.sh`. ### Tier 3: Read-Only via MCP-LNC The MCP server connects to a Lightning node through an encrypted LNC tunnel using a 10-word pairing phrase. No credentials are written to disk. The pairing phrase is handled in memory and an ephemeral ECDSA keypair is generated per session. When the session ends, the keypair is discarded. This tier exposes 18 read-only tools. The agent can query balances, list channels, decode invoices, and inspect the network graph, but it cannot send payments, open channels, or modify any node state. **If the agent machine is compromised,** the attacker gains read access to the node's state for the duration of the active LNC session. Once the session is closed, no credentials remain to reconnect. **Setup:** Use the `lightning-mcp-server` skill. See [MCP Server](mcp-server.md) for the setup walkthrough. ## Remote Signer in Depth The remote signer splits a Lightning node into two processes. The signer runs lnd with the seed and private keys but does not connect to the peer-to-peer network, does not route payments, and does not manage channels. The watch-only node does everything else. By default, both run in Docker containers (`litd-signer` and `litd` respectively); pass `--native` to scripts for local binary mode. ### Credential Bundle When you run `setup-signer.sh`, the signer creates a wallet and exports a credentials bundle to `~/.lnget/signer/credentials-bundle/`: | File | What it contains | What it's used for | |------|-----------------|-------------------| | `accounts.json` | Account xpubs (public keys) | Watch-only wallet creation | | `tls.cert` | Signer's TLS certificate | Authenticating the gRPC connection | | `admin.macaroon` | Signer's admin macaroon | Authorizing signing RPCs | A base64 tarball (`credentials-bundle.tar.gz.b64`) is also generated for transfer. On the agent machine, `import-credentials.sh` unpacks it into `~/.lnget/lnd/signer-credentials/`. ### Signing Protocol The watch-only node constructs transactions locally and sends them to the signer for signature via gRPC. The signer validates each request, signs with the appropriate key, and returns the signature. The watch-only node then assembles and broadcasts the signed transaction. ```mermaid sequenceDiagram participant WO as Watch-Only lnd
(agent machine) participant S as Signer lnd
(secure machine) participant Net as Bitcoin Network Note over WO: Needs to close a channel WO->>WO: Build unsigned commitment tx WO->>S: SignOutputRaw(unsigned_tx, key_desc) S->>S: Look up private key S->>S: Validate request S->>S: Produce signature S-->>WO: Signature bytes WO->>WO: Attach signature to tx WO->>Net: Broadcast signed tx ``` The gRPC connection between the watch-only node and the signer is secured with mutual TLS. The watch-only node uses the signer's exported TLS certificate (`tls.cert`) to verify the server's identity. The macaroon provides authorization. ### Hardening the Signer For production deployments: - **Scope the signer macaroon.** Replace `admin.macaroon` with a `signer-only` macaroon baked via `macaroon-bakery`. This restricts the watch-only node to signing operations and key derivation. It cannot call any other RPC on the signer. ```bash # Container mode (auto-detects litd-signer) skills/macaroon-bakery/scripts/bake.sh --role signer-only --container litd-signer # Native mode skills/macaroon-bakery/scripts/bake.sh --role signer-only \ --rpc-port 10012 --lnddir ~/.lnd-signer ``` - **Firewall the signer.** Only the watch-only node's IP should be able to reach port 10012. Block all other inbound traffic. - **Dedicate the hardware.** Run the signer on a separate machine or hardened VM with no other services. Minimize the installed software and disable remote access except through a controlled management channel. - **Rotate macaroons.** Bake new macaroons periodically and update the watch-only node's configuration. The old root key can be revoked to invalidate the previous macaroon. ## Macaroon Security Macaroons are bearer tokens. Anyone who has a copy of a macaroon can exercise its permissions against the lnd node that issued it. Treat them like passwords: store them with restrictive file permissions (0600), don't commit them to version control, and don't transmit them over unencrypted channels. ### Preset Roles The `macaroon-bakery` skill provides five preset roles that cover common agent use cases. Each role grants the minimum set of RPC permissions needed: | Role | Permissions granted | Typical use case | |------|-------------------|-----------------| | `pay-only` | `SendPaymentSync`, `DecodePayReq`, `GetInfo` | Agent that buys L402 resources via lnget | | `invoice-only` | `AddInvoice`, `LookupInvoice`, `ListInvoices`, `GetInfo` | Agent that sells resources via aperture | | `read-only` | `GetInfo`, `WalletBalance`, `ChannelBalance`, `ListChannels`, `ListPeers`, `ListPayments`, `ListInvoices` | Monitoring and reporting | | `channel-admin` | Everything in `read-only` + `OpenChannelSync`, `CloseChannel`, `ConnectPeer` | Node management | | `signer-only` | `SignOutputRaw`, `ComputeInputScript`, `MuSig2Sign`, `DeriveKey`, `DeriveNextKey` | Remote signer credentials | ### Custom Macaroons For permissions that don't fit a preset role, bake a custom macaroon with specific URI permissions: ```bash skills/macaroon-bakery/scripts/bake.sh --custom \ uri:/lnrpc.Lightning/SendPaymentSync \ uri:/lnrpc.Lightning/DecodePayReq \ uri:/lnrpc.Lightning/WalletBalance \ uri:/lnrpc.Lightning/GetInfo ``` The full list of available permission URIs is available via: ```bash skills/macaroon-bakery/scripts/bake.sh --list-permissions ``` ### Rotation Macaroon rotation involves baking a new macaroon, updating the agent's configuration to use it, and optionally revoking the old root key: ```bash # Bake replacement skills/macaroon-bakery/scripts/bake.sh --role pay-only \ --save-to ~/pay-only-v2.macaroon # Update agent config to point at the new macaroon # Revoke old root key (invalidates all macaroons baked with it) skills/lnd/scripts/lncli.sh bakemacaroon --root_key_id 0 ``` ## Production Checklist Before deploying the kit with real funds: 1. **Use the remote signer.** Set up `lightning-security-module` on a separate machine. Run the agent's lnd in watch-only mode. Do not use standalone mode for production. 2. **Scope all macaroons.** Bake role-specific macaroons for each agent. Never distribute `admin.macaroon`. A buyer agent gets `pay-only`; a seller agent gets `invoice-only`; a monitoring agent gets `read-only`. 3. **Scope the signer macaroon.** Replace the signer's `admin.macaroon` with a `signer-only` macaroon. The watch-only node only needs signing and key derivation permissions on the signer. 4. **Firewall the signer.** Restrict port 10012 to the watch-only node's IP. In container mode, port 10013 (REST) is bound to `0.0.0.0` for Docker networking but is only host-mapped during wallet setup. In native mode, `setup-signer.sh` rebinds REST to `localhost`. 5. **Secure credential files.** Verify that `wallet-password.txt`, `seed.txt`, and all `.macaroon` files have mode 0600. The kit's scripts set this automatically, but verify after any manual operations. 6. **Set spending limits.** Configure `--max-cost` on lnget commands to cap per-request spending. Monitor wallet balances programmatically. 7. **Back up the seed.** The signer's `seed.txt` is the only way to recover funds if the signer's storage fails. Store the 24-word mnemonic securely offline. ## Credential File Reference Every credential and secret file the kit manages: | File | Mode | Machine | Purpose | |------|------|---------|---------| | `~/.lnget/lnd/wallet-password.txt` | 0600 | Agent | lnd wallet unlock passphrase | | `~/.lnget/lnd/seed.txt` | 0600 | Agent | 24-word mnemonic (standalone only) | | `~/.lnget/lnd/signer-credentials/tls.cert` | 0644 | Agent | Signer's TLS cert | | `~/.lnget/lnd/signer-credentials/admin.macaroon` | 0600 | Agent | Signer RPC auth | | `~/.lnget/lnd/signer-credentials/accounts.json` | 0600 | Agent | Account xpubs | | `~/.lnget/signer/wallet-password.txt` | 0600 | Signer | Signer wallet passphrase | | `~/.lnget/signer/seed.txt` | 0600 | Signer | Signer 24-word mnemonic | | `~/.lnd/data/chain/bitcoin//admin.macaroon` | 0600 | Agent | lnd admin macaroon | | `~/.lnd-signer/data/chain/bitcoin//admin.macaroon` | 0600 | Signer | Signer admin macaroon | | `~/.lnget/tokens//` | 0700 | Agent | Cached L402 tokens | | `~/.aperture/aperture.yaml` | 0600 | Seller | Aperture config (may contain credentials) | **Container mode note:** In container deployments, daemon data directories (`~/.lnd/`, `~/.lnd-signer/`) live inside Docker volumes (`litd-data`, `signer-data`) rather than on the host filesystem. The host-side credential files under `~/.lnget/` remain the same in both modes. Use `docker cp` or `export-credentials.sh --container` to extract macaroons and TLS certs from running containers.