[feature]: don't return `r_preimage` for unsettled invoices to `invoices:read` callers
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.mdpresents 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 needsSubscribeSingleInvoice), and naming methods throughuri: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.
Source: lightningnetwork/lnd