#11196·lnd

[feature]: don't return `r_preimage` for unsettled invoices to `invoices:read` callers

Author: MPinsCreated Sep 15, 2026Updated Sep 15, 2026
Labelsenhancement

Is your feature request related to a problem? Please describe. A macaroon holding only invoices:read can read the preimage of an invoice that has not been paid yet.

LookupInvoice, ListInvoices and SubscribeInvoices require only that permission, and they build their responses with CreateRPCInvoice, which sets RPreimage whenever the invoice has a stored preimage, without looking at the invoice state (lnrpc/invoicesrpc/utils.go). A regular invoice has one from creation, since AddInvoice generates it so the node can settle on its own; hold and AMP invoices store none and are unaffected. invoices:read is part of both the default readonly.macaroon and invoice.macaroon.

Verified on regtest (master at 90ea05d5a; the code path has been in place since at least v0.6.0-beta): a macaroon baked with only invoices:read is denied AddInvoice, yet lookupinvoice and listinvoices return an open invoice with an r_preimage whose sha256 equals its r_hash.

The preimage of an unpaid invoice is what lets a node on the payment path settle the incoming HTLC without forwarding it, so a read-only credential is more powerful than it looks whenever its holder is also a channel peer or an LSP. I raised this with [email protected] first; they do not consider it a security issue, since it requires a macaroon in the first place, which is why I am opening it here instead.

Describe the solution you'd like Include r_preimage in invoice responses only once the invoice is settled. If the current behaviour needs to be preserved for the node operator, an alternative would be to include it only for callers that also hold invoices:write.

Describe alternatives you've considered

  • Documenting it only. docs/macaroons.md presents read entities as harmless, so a note there would help, but it leaves the default macaroons as they are.
  • Narrowing permissions when delegating. This does not work today: a service that issues invoices needs invoices:read (the hold invoice flow needs SubscribeSingleInvoice), and naming methods through uri: does not change what a response contains.

Additional context Other implementations do not expose it. Core Lightning adds payment_preimage only when status == paid (lightningd/invoice.c, and doc/schemas/listinvoices.json, where the field is described as "Proof of payment"). LDK derives the preimage from the node's key material at claim time rather than storing it.

Happy to open a PR if there is agreement on a direction.