From e96ad1cea4192a608a7f15896e4b690b57bf8fc9 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 29 Sep 2026 08:37:39 -0700 Subject: invoices: don't let a supplied id overwrite another user's invoice generate() writes invoice: wholesale with the caller's uid, and the old bolt11 pointer keeps resolving to that id, so passing someone else's invoice id re-pointed their invoice at the caller's account. Invoice ids aren't secret (LUD-21 verify URLs, invoice pages, receipts), so refuse an existing id that belongs to a different user, both in generate() and in POST /invoice for anonymous pay-page callers. Both fail with "invoice not found" so ids can't be probed for existence. Co-Authored-By: Claude Fable 5.1 --- lib/invoices.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib') diff --git a/lib/invoices.ts b/lib/invoices.ts index ba55b77c..7f25f4ba 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -84,6 +84,17 @@ export const generate = async ({ invoice, user }) => { if (memo && memo.length > 5000) fail("memo too long"); if (!id) id = v4(); + else { + // A caller-supplied id re-uses an existing record (tip/webhook update, + // repeated bolt12 offer create). It must be the caller's own: the write + // below replaces `invoice:` wholesale with uid = this user, and the + // old bolt11 pointer keeps resolving to the same id, so overwriting + // another user's invoice re-routes whatever their payer sends into this + // account. Same error as a missing invoice so ids can't be probed for + // existence. + const existing = await getInvoice(id); + if (existing && existing.uid !== user.id) fail("invoice not found"); + } let hash; let text; -- cgit v1.2.3