diff options
| author | Adam Soltys <[email protected]> | 2026-07-30 10:28:23 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-30 10:38:43 -0700 |
| commit | fd1617ea66e01557e71debc7e45ca08cfe01bf70 (patch) | |
| tree | 8c3600a86d60dc7731130397c98e818970d63990 /routes | |
| parent | f5a5b1ff856efcd2849275a301daabb80ef76638 (diff) | |
| download | coinos-server-fd1617ea66e01557e71debc7e45ca08cfe01bf70.tar.gz coinos-server-fd1617ea66e01557e71debc7e45ca08cfe01bf70.zip | |
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 <[email protected]>
Diffstat (limited to 'routes')
| -rw-r--r-- | routes/payments.ts | 14 |
1 files changed, 11 insertions, 3 deletions
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); } }, |
