xpay: handle localinvreqid and label parameters when we're called as `pay`.
What changed, and why it matters
This change fixes a small compatibility gap in the xpay plugin, which can take over the older `pay` command. Previously, if a user supplied a `label` or `localinvreqid` argument to `pay`, xpay would silently drop those arguments before forwarding the payment. Now it preserves and forwards them. This is mainly a functional bug fix; the dropped arguments could cause user labels or local invoice request tracking to be lost, but there is no direct evidence of a security vulnerability.
No urgent security action. Treat as a normal functional fix. Reviewers may want to confirm that forwarded `label` and `localinvreqid` values are properly validated by the underlying `xpay` implementation and that no other `pay` parameters are silently dropped in a way that affects correctness or accounting.
Security signals we found
Argument forwarding omission in RPC wrapper/plugin
Potential loss of payment metadata (label/localinvreqid) when legacy command is redirected
No input validation or sanitization changes visible in diff
Evidence from the diff
The xpay plugin intercepts RPC calls to the legacy pay method and rewrites them as xpay calls. The original code only recognized a subset of pay parameters (bolt11, amount_msat, partial_msat, retry_for, maxdelay, maxfee, exemptfee). When it encountered label or localinvreqid it logged ‘Unknown arg …, xpay will ignore it.’ and discarded them. The patch adds parsing for label and localinvreqid and re-emits them in the rewritten JSON payload. A test is updated to assert that the label survives the redirect.
Changed components
plugins/xpay/xpay.ctests/test_xpay.pyInspect captured patch +12 / −3
diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c
index d515d3b8..0e26a569 100644
--- a/plugins/xpay/xpay.c
+++ b/plugins/xpay/xpay.c
@@ -2562,7 +2562,7 @@ static struct command_result *handle_rpc_command(struct command *cmd,
struct xpay *xpay = xpay_of(cmd->plugin);
const jsmntok_t *rpc_tok, *method_tok, *params_tok, *id_tok,
*bolt11 = NULL, *amount_msat = NULL,
- *partial_msat = NULL, *retry_for = NULL, *maxdelay = NULL;
+ *partial_msat = NULL, *retry_for = NULL, *maxdelay = NULL, *localinvreqid = NULL, *label = NULL;
const char *maxfee = NULL;
struct json_stream *response;
@@ -2623,6 +2623,10 @@ static struct command_result *handle_rpc_command(struct command *cmd,
exemptfee = t + 1;
else if (json_tok_streq(buf, t, "maxdelay"))
maxdelay = t + 1;
+ else if (json_tok_streq(buf, t, "label"))
+ label = t + 1;
+ else if (json_tok_streq(buf, t, "localinvreqid"))
+ localinvreqid = t + 1;
else {
plugin_log(cmd->plugin, LOG_INFORM,
"Unknown arg %.*s, xpay will ignore it.",
@@ -2669,6 +2673,10 @@ static struct command_result *handle_rpc_command(struct command *cmd,
json_add_tok(response, "partial_msat", partial_msat, buf);
if (maxdelay)
json_add_tok(response, "maxdelay", maxdelay, buf);
+ if (label)
+ json_add_tok(response, "label", label, buf);
+ if (localinvreqid)
+ json_add_tok(response, "localinvreqid", localinvreqid, buf);
json_object_end(response);
json_object_end(response);
return command_finished(cmd, response);
diff --git a/tests/test_xpay.py b/tests/test_xpay.py
index d4eeef6a..6cada7a8 100644
--- a/tests/test_xpay.py
+++ b/tests/test_xpay.py
@@ -460,12 +460,13 @@ def test_xpay_takeover(node_factory, executor):
inv, "10000", 'label'])
l1.daemon.wait_for_log(r'Not redirecting pay \(only handle 1 or 2 args\): ')
- # Other args are ignored.
+ # Label gets maintained.
inv = l3.rpc.invoice('any', "test_xpay_takeover7", "test_xpay_takeover7")
l1.rpc.pay(inv['bolt11'], amount_msat=10000, label='test_xpay_takeover7')
- l1.daemon.wait_for_log('Unknown arg "label", xpay will ignore it.')
l1.daemon.wait_for_log('Redirecting pay->xpay')
+ assert any(p.get('label') == 'test_xpay_takeover7' for p in l1.rpc.listsendpays()['payments'])
+ # Other args are ignored.
inv = l3.rpc.invoice('any', "test_xpay_takeover8", "test_xpay_takeover8")
l1.rpc.pay(inv['bolt11'], amount_msat=10000, riskfactor=1)
l1.daemon.wait_for_log('Unknown arg "riskfactor", xpay will ignore it.')
Why this scored 18/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.