Describe the bug
The /callbacks/opennode accepts any incoming request without verifying its authenticity. Every other callback handler (Zebedee, LNbits, Nodeless) validates the source , OpenNode is the only one that doesn't.
This has two problems:
-
No HMAC check. OpenNode signs every webhook with a hashed_order field (HMAC-SHA256(api_key, charge_id)). The endpoint ignores it entirely.
-
Wrong body parser. OpenNode sends webhooks as application/x-www-form-urlencoded (per their docs), but the route registers json() middleware only. The body is silently unparsed for real OpenNode requests, so even if validation were added, it would always operate on an empty object.
An attacker with a valid invoice ID can POST a fake status: paid payload and permanently corrupt that invoice's state in the database, making it uncompletable by the real payer.
To Reproduce
- Stand up the relay with OpenNode as the payment processor.
- Grab a valid invoice ID from the connection/invoice flow.
- Run:
curl -X POST https://<your-relay>/callbacks/opennode \
--data "id=<invoice_id>&status=paid&order_id=<any_pubkey>&hashed_order=fakehash"
- Check the
invoices table , status is now COMPLETED with no payment made.
Expected behavior
The endpoint should parse application/x-www-form-urlencoded bodies (as OpenNode sends them) and reject any request where hashed_order does not match HMAC-SHA256(OPENNODE_API_KEY, charge_id), which is what OpenNode's own documentation specifies and what the other handlers already do for their respective authentication schemes.
Screenshots
N/A
Describe the bug
The
/callbacks/opennodeaccepts any incoming request without verifying its authenticity. Every other callback handler (Zebedee, LNbits, Nodeless) validates the source , OpenNode is the only one that doesn't.This has two problems:
No HMAC check. OpenNode signs every webhook with a
hashed_orderfield (HMAC-SHA256(api_key, charge_id)). The endpoint ignores it entirely.Wrong body parser. OpenNode sends webhooks as
application/x-www-form-urlencoded(per their docs), but the route registersjson()middleware only. The body is silently unparsed for real OpenNode requests, so even if validation were added, it would always operate on an empty object.An attacker with a valid invoice ID can POST a fake
status: paidpayload and permanently corrupt that invoice's state in the database, making it uncompletable by the real payer.To Reproduce
invoicestable ,statusis nowCOMPLETEDwith no payment made.Expected behavior
The endpoint should parse
application/x-www-form-urlencodedbodies (as OpenNode sends them) and reject any request wherehashed_orderdoes not matchHMAC-SHA256(OPENNODE_API_KEY, charge_id), which is what OpenNode's own documentation specifies and what the other handlers already do for their respective authentication schemes.Screenshots
N/A