From fd1617ea66e01557e71debc7e45ca08cfe01bf70 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 30 Jul 2026 10:28:23 -0700 Subject: mint/lightning: refuse caller-supplied invoices, tiered fees, log hygiene - invoices: reject caller-supplied bolt11/bolt12; the server always mints its own. Closes the invoice-registration path behind the shared-node credit and payment-hijack vectors. - lightning: skip mint-owned invoices (lbl* label) in the paid-invoice listener. - payments: lightning free-tier fee credit, withheld for flagged high-throughput (ln:nofree) accounts which pay an elevated rate; the failed-send reversal restores credit at the same rate. freezeCheck made crash-resilient. Lightning and bolt12 sends stay pending until the preimage settles. - payments (route): validate payment-request / invoice input types. - logging: collapse repeated xpay failure lines into one. Co-Authored-By: Claude Opus 4.8 --- routes/payments.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'routes/payments.ts') diff --git a/routes/payments.ts b/routes/payments.ts index 4239a409..9adc10ee 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -5,7 +5,7 @@ import { archive, db, g, gf, s, sa } from "$lib/db"; import { generate, getUserOffer } from "$lib/invoices"; import { replay } from "$lib/lightning"; import ln from "$lib/ln"; -import { err, l, warn } from "$lib/logging"; +import { err, l, shortError, warn } from "$lib/logging"; import mqtt from "$lib/mqtt"; import { build, @@ -45,6 +45,14 @@ export default { const balance = await g(`balance:${user.id}`); try { + // Reject non-string payreq/hash up front. A client sending a number/object + // here otherwise reaches a decoder that calls `.startsWith` on it and throws + // an opaque "n.startsWith is not a function" (logged bare via the catch + // below). A malformed *string* is fine — it fails later with a clear error. + if (payreq != null && typeof payreq !== "string") + fail("Invalid payment request"); + if (hash != null && typeof hash !== "string") fail("Invalid invoice"); + if (typeof amount !== "undefined") { amount = Number.parseInt(amount); if (amount < 0 || amount > SATS || Number.isNaN(amount)) @@ -93,7 +101,7 @@ export default { res.send(p); } catch (e) { warn(user.username, "payment failed", amount, balance, hash, payreq); - err(e.message); + err(shortError(e.message)); bail(res, e.message); } }, @@ -578,7 +586,7 @@ export default { res.send(p); } catch (e) { - warn(user.username, "payment failed", e.message); + warn(user.username, "payment failed", shortError(e.message)); res.code(500).send(e.message); } }, -- cgit v1.2.3