bolthub logobolthub
Security

Security

How bolthub protects your data, credentials, and API traffic.

NWC credential encryption

Tenant NWC connection URIs are encrypted at rest using AES-256-GCM with a unique random IV per credential. Credentials are decrypted only at the moment they are needed (e.g. to create an invoice) and are never logged or cached in plaintext.

SSRF protection

All outbound HTTP requests (e.g. proxying to tenant origins, NWC relay connections, wallet configuration URLs) pass through a multi-layer SSRF protection system:

  • Private IP blocking: all private, reserved, and link-local IP ranges are blocked, including cloud metadata endpoints.
  • Alternative encoding detection: private IPs encoded in decimal, hexadecimal, octal, or IPv6-mapped formats are detected and blocked.
  • DNS rebinding prevention: after static checks pass, hostnames are resolved via DNS and all resolved addresses are validated against the blocklist.
  • Wallet URL validation: when tenants configure Lightning wallet connections (LND, LNbits, NWC, Phoenixd, and similar), URLs are validated against the SSRF blocklist before being stored.

L402 token security

L402 tokens are macaroons (issued in WWW-Authenticate challenges), secured with:

  • Per-tenant root keys: each macaroon is signed (an HMAC-SHA256 chain) with a root key derived per tenant via domain-separated HMAC, so a leaked or compelled key affects one tenant, not the fleet.
  • First-party caveats bind the payment hash, tenant, endpoint, and expiry; verification fails closed on any caveat it does not recognise.
  • Offline attenuation: a holder can narrow a macaroon (a single HTTP method, a tighter expiry, a use count, a spend cap, a path subtree) and delegate it to a sub-agent without contacting bolthub. The gateway enforces every caveat down the chain.
  • Preimage verification: the payment preimage is verified against the original payment hash using constant-time comparison to prevent timing side-channel attacks.
  • Generic error messages: all authentication failures return a uniform error, preventing attackers from distinguishing between failure modes.

Scoped delegation

Delegation lets a parent agent hand a sub-agent a capped credential instead of its wallet. The security properties:

  • Tighten-only. A child credential can only narrow: it cannot raise a use count or spend cap, move the expiry later, or widen the path scope. The SDK rejects a widening attenuation up front, and the gateway verifier independently folds the chain (minimum use count and spend cap, longest path prefix), so even a hand-crafted child that tries to widen fails verification. A child can never escalate above its parent.
  • Path scope is exact. path_prefix matches on a segment boundary and is case-sensitive, so /v1/reports grants /v1/reports/42 but not /v1/reportsX. Percent-escapes are decoded once and any literal .. is rejected, so an attenuated path cannot be tricked into escaping its subtree.
  • Server-side meter. Use counts and spend caps are enforced by a grant row keyed by the payment hash, so the limits hold across processes and gateway instances, not just within one client. The burn also re-checks the tenant and endpoint, so a credential can only ever draw down the grant it was minted for.
  • Budget interlock. When a client mints a child with a spend cap, that cap is reserved from the parent's budget at mint time, so a parent and all its children can never jointly spend more than the parent intended.
  • Tree revocation. Revoking a grant (present its credential to the gateway's revoke endpoint) flips it inactive, so the parent and every child minted from it fail on their next request within one cache interval (about 15 seconds). Revocation is tree-level: there is no per-child revocation, because children are minted offline and share one grant.
  • Bearer revocation auth. The right to revoke is proof of payment: whoever knows the credential's preimage can revoke its grant. This follows L402's bearer model and only denies further use of already-paid sats, never moves money.

Prepaid credit scope

Prepaid credit lets a buyer spend one payment across a provider's endpoints. It widens a credential's scope from one endpoint to one tenant, and the widening is deliberate and signed, never inferred:

  • Tenant-bound, never cross-tenant. A credit token binds tenant_id and carries an explicit signed scope=credit marker; root keys are derived per tenant, so a credit token cannot verify against, or spend at, any other provider. The endpoint check is relaxed only when that signed marker is present, never because an endpoint is absent.
  • The budget comes from the invoice, not the token. Macaroon caveats can be appended by any holder (that is what attenuation is), so the credit grant's max_sats is taken from the paying invoice's recorded credit_sats, never from a max_sats caveat. A token presenting a scope=credit caveat with no credit purchase behind it mints no grant (the payment is not even consumed). This is why appending scope=credit to a single-use token buys nothing.
  • Server-metered, non-custodial. Every call burns the endpoint's real price against the budget (no cross-endpoint arbitrage), and a single Lightning payment settles to the provider's own wallet — bolthub never holds a pooled balance.

The honest trade-off: a leaked credit token exposes more of one provider's surface (bounded by its budget, path_prefix, expiry, and revocation) than a single-endpoint token would. It never exposes another provider and never exceeds the paid budget.

Session token security

For billing models that use session tokens (time_pass, metered, token_bucket, per_kb):

  • Session tokens are cryptographically signed and scoped to a specific tenant and endpoint.
  • Session tokens include an embedded expiry, enforced server-side during verification.
  • Time pass sessions expire after the purchased duration. Metered sessions track remaining balance and reject requests when the balance is exhausted.

Payment receipts and preimages

A preimage is proof of a Lightning payment: whoever holds it can prove the invoice was paid. Two receipt paths exist, with different retention.

  • Buyer-side (SDK/MCP): receipts are written only when you configure a store, and they live entirely on your machine (~/.bolthub/receipts.jsonl by default, 0600). bolthub never receives them. A receipt file carries live preimages, so treat it like a credential. When sharing a receipt as an expense record, export with redaction: the invoice, amount, and payment hash remain, the preimage is stripped, and the record still identifies the payment without carrying its proof.
  • Seller-side (gateway): the gateway captures a preimage on the paid invoice row only when GATEWAY_PERSIST_PREIMAGES is enabled, and only after the payment is consumed (the replay window is already closed by consumption and the token's expiry, so a stored preimage cannot be replayed for access). Capture is off by default. When on, the preimage is written once and never overwritten or logged; retention follows the invoice row's own lifecycle. Invoices consumed before capture was enabled have no preimage, and no backfill is possible.

Facilitator API keys

Seller API keys for the hosted facilitator are designed to limit blast radius:

  • Shown once: the full key is displayed only at creation time, never again.
  • Hashed at rest: only a peppered HMAC hash of each key is stored; the plaintext is never persisted.
  • Immediate revocation and rotation: revoking or rotating a key takes effect immediately on the instance serving the request, and within a bounded TTL (30 seconds or less) across all instances.
  • Optional expiry: keys can be created with an expiry, enforced on every request.
  • Audit logging: all key lifecycle events (creation, rotation, revocation) are audit-logged.

Access control

All tenant-scoped API routes verify the authenticated user owns the requested resource. Routes that operate on endpoints include an additional ownership check to prevent unauthorized access to other tenants' data.

Rate limiting

Both the API and the Go gateway apply multiple layers of rate limiting:

  • Paying traffic is bounded by economics, not IP. Requests carrying payment credentials skip the per-IP and per-endpoint limits. Their cost control is single-use invoices and prepaid session budgets. A high per-IP ceiling remains purely as an abuse backstop, and IPs that repeatedly present forged credentials are demoted to the anonymous limits for the window.
  • Per-IP rate limiting on anonymous traffic (probes and payment challenges), per endpoint and globally.
  • Per-feature limits on expensive operations: invoice creation, wallet connections, webhooks, listings, and the playground tester each draw from their own isolated budget.
  • Every 429 carries Retry-After, and the official SDKs honor it automatically.
  • Shared Redis backend: when Upstash Redis is configured, rate limit counters are shared across all API and gateway replicas so that limits are enforced globally. If Redis is unavailable, each instance falls back to independent in-memory limits automatically.

Row Level Security (RLS)

All tenant-scoped database tables are protected by Row Level Security policies:

  • Tenants can only read and modify their own data.
  • Billing cycles, endpoints, and credentials are scoped to the authenticated tenant.
  • Public tables (e.g. directory listings) use RLS to restrict reads to approved entries.
  • Service-role access is restricted to the API backend.

HMAC request signing

Requests from the gateway to tenant origins are signed with HMAC-SHA256. The signature, timestamp, and nonce are included as headers on the proxied request. Tenant origins can verify the signature to ensure requests come from the bolthub gateway and have not been tampered with.

Security headers

All HTTP responses include hardened security headers including Content-Security-Policy, Strict-Transport-Security, X-Frame-Options, Cross-Origin-Opener-Policy, X-Content-Type-Options, and Referrer-Policy.

Proxy header sanitization

Before forwarding requests to tenant origins, the gateway strips sensitive headers including forwarding headers (X-Forwarded-For, X-Real-IP), CDN headers, cookies, and the L402 Authorization header. The Go gateway additionally strips response headers from the origin (Set-Cookie, Server, X-Powered-By, Strict-Transport-Security, Content-Security-Policy, Public-Key-Pins, Alt-Svc) to prevent origin infrastructure leakage.

Body size limits

Request bodies are limited to 1 MB and response bodies are limited to 10 MB. Requests exceeding the request body limit are rejected with a 413 Payload Too Large response. Responses exceeding the response body limit are rejected with a 502 Bad Gateway error.

Response streaming

Uncached proxy responses are streamed directly to the client rather than buffered in memory, with a 50 MB cap per response. Cached GET responses are buffered within the 10 MB response body limit.

Circuit breaker

The gateway implements a circuit breaker per endpoint to protect against cascading failures from unhealthy origins. After 5 consecutive failures (5xx responses or connection errors), the circuit opens and requests are immediately rejected with 502 Service temporarily unavailable for 30 seconds. After the timeout, the circuit enters a half-open state allowing 2 test requests before fully closing.