xpay: use filtering on rpc_command so we only get called on "pay".
What changed, and why it matters
This change is a performance optimization for the xpay plugin in Core Lightning. Previously, the plugin's hook was called for every RPC command, which slowed things down. Now it only listens for the 'pay' command. There is no direct security vulnerability here, but running a hook on every command can slightly increase attack surface and resource use.
No immediate security action required. This is a defensive hardening/performance improvement. Reviewers may want to confirm the filter correctly covers all intended 'pay' command variants and does not bypass array-form RPC calls.
Security signals we found
Reduction of hook invocation scope from all RPC commands to only 'pay'
Performance improvement reduces potential denial-of-service resource consumption
Test added to verify plugin is not called for unrelated RPC commands
Evidence from the diff
The commit adds string filtering to the rpc_command hook in plugins/xpay/xpay.c, restricting it to only trigger on the ‘pay’ command. This reduces unnecessary hook invocations and improves performance, as shown by the test timing stats. A test is added to confirm xpay is not invoked for unrelated RPC commands like ‘help’.
Changed components
plugins/xpay/xpay.ctests/test_xpay.pyInspect captured patch +11 / −2
diff --git a/plugins/xpay/xpay.c b/plugins/xpay/xpay.c
index 78bb17cd..70001888 100644
--- a/plugins/xpay/xpay.c
+++ b/plugins/xpay/xpay.c
@@ -2500,10 +2500,13 @@ dont_redirect:
return command_hook_success(cmd);
}
+static const char *cmd_hook_filters[] = {"pay"};
static const struct plugin_hook hooks[] = {
{
- "rpc_command",
- handle_rpc_command,
+ .name = "rpc_command",
+ .handle = handle_rpc_command,
+ .strfilters = cmd_hook_filters,
+ .num_strfilters = ARRAY_SIZE(cmd_hook_filters),
},
};
diff --git a/tests/test_xpay.py b/tests/test_xpay.py
index 087ade6b..ed24a4b7 100644
--- a/tests/test_xpay.py
+++ b/tests/test_xpay.py
@@ -387,8 +387,14 @@ def test_xpay_takeover(node_factory, executor):
# Simple bolt11/bolt12 payment.
inv = l3.rpc.invoice(100000, "test_xpay_takeover1", "test_xpay_takeover1")['bolt11']
l1.rpc.pay(inv)
+ l1.daemon.wait_for_log('Calling rpc_command hook of plugin cln-xpay')
l1.daemon.wait_for_log('Redirecting pay->xpay')
+ # Quickly test that xpay does NOT receive other commands now.
+ l1.rpc.help()
+ assert not l1.daemon.is_in_log('Calling rpc_command hook of plugin cln-xpay',
+ start=l1.daemon.logsearch_start)
+
# Array version
inv = l3.rpc.invoice(100000, "test_xpay_takeover2", "test_xpay_takeover2")['bolt11']
subprocess.check_output(['cli/lightning-cli',
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.