From c07dfe050b7c79917744d7eddbf934d319cb9c89 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 7 Aug 2026 23:00:07 -0700 Subject: payments: never lose a settled deposit to an oversized memo (S9/S10) credit() ran fail("memo too long") on memos >5000 chars, but it runs AFTER settlement: an incoming lightning/bolt12 deposit has already advanced pay_index, and an internal send has already debited the sender. The throw was only logged, replay() hit the same throw, and the sats sat in the house wallet uncredited. The memo is attacker-controllable and was uncapped via the lnurl comment param and the bolt12 payer_note. Truncate the memo in credit() instead of throwing (a clipped note is harmless), and cap the lnurl comment to the advertised 512 at ingress. Co-Authored-By: Claude Opus 4.8 --- routes/lnurl.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'routes') diff --git a/routes/lnurl.ts b/routes/lnurl.ts index 1acc4d76..1b3f84c1 100644 --- a/routes/lnurl.ts +++ b/routes/lnurl.ts @@ -188,7 +188,10 @@ export default { }); if (comment) { - invoice.memo = comment; + // Enforce the commentAllowed:512 we advertise in the lnurlp response. + // This overwrite happens after generate()'s own memo validation, so an + // uncapped comment would otherwise reach credit() at settlement time. + invoice.memo = String(comment).slice(0, 512); await s(`invoice:${invoice.id}`, invoice); } -- cgit v1.2.3