ipc: Expose an RPC interface over the -ipcbind socket
What changed, and why it matters
This commit adds a new way for local programs to talk to Bitcoin Core: instead of connecting over a regular network port, they can connect through a Unix socket file using a feature called -ipcbind. The commit itself is a building block for a later change that will let bitcoin-cli use this socket. The code exposes the same RPC commands that already exist, but over the local socket. The commit also updates the help text to warn that any local process that can reach the socket gets unauthenticated RPC access, so the socket file's permissions matter.
Treat this as a normal feature commit with security-relevant configuration implications. Review the default socket path and permissions created by -ipcbind, ensure the documentation clearly warns users about unauthenticated local access, and verify that follow-up commits (especially bitcoin-cli IPC support) enforce least-privilege socket permissions and do not expose the socket in unsafe directories.
Security signals we found
New IPC RPC interface bypasses HTTP/TCP listener and any HTTP-layer authentication when accessed over the -ipcbind Unix socket
Help text acknowledges 'unauthenticated RPC access' for local processes that can access the socket
RpcImpl::executeRpc forwards to ExecuteHTTPRPC with a user-supplied authUser string, relying on caller-side trust model for IPC
No visible authorization check inside RpcImpl; access control is delegated to filesystem permissions on the Unix socket
Change is a feature addition, not a patch for a reported vulnerability
Evidence from the diff
The patch introduces an interfaces::Rpc abstraction and a Cap’n Proto RPC definition (rpc.capnp) so that an IPC client can call executeRpc() on the node process. It wires the new interface into Init::makeRpc() for bitcoin-gui and bitcoin-node, and implements RpcImpl in src/node/interfaces.cpp, which forwards to ExecuteHTTPRPC(). The -ipcbind help text is updated to note that enabling it grants unauthenticated RPC access to any local process that can access the socket. The change is explicitly described as enabling future bitcoin-cli IPC connectivity, not as a security fix.
Changed components
src/init.cpp (argument help text)src/init/bitcoin-gui.cppsrc/init/bitcoin-node.cppsrc/interfaces/init.hsrc/interfaces/rpc.h (new)src/ipc/capnp/init.capnpsrc/ipc/capnp/rpc.capnp (new)src/node/interfaces.cppInspect captured patch +95 / −1
diff --git a/src/init.cpp b/src/init.cpp
index 6a6e7a92..da024bdd 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -719,7 +719,7 @@ void SetupServerArgs(ArgsManager& argsman, bool can_listen_ipc)
argsman.AddArg("-rpcworkqueue=<n>", strprintf("Set the maximum depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::RPC);
argsman.AddArg("-server", "Accept command line and JSON-RPC commands", ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
if (can_listen_ipc) {
- argsman.AddArg("-ipcbind=<address>", "Bind to Unix socket address and listen for incoming connections. Valid address values are \"unix\" to listen on the default path, <datadir>/node.sock, or \"unix:/custom/path\" to specify a custom path. Can be specified multiple times to listen on multiple paths. Default behavior is not to listen on any path. If relative paths are specified, they are interpreted relative to the network data directory. If paths include any parent directory components and the parent directories do not exist, they will be created.", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
+ argsman.AddArg("-ipcbind=<address>", "Bind to Unix socket address and listen for incoming connections. Valid address values are \"unix\" to listen on the default path, <datadir>/node.sock, or \"unix:/custom/path\" to specify a custom path. Can be specified multiple times to listen on multiple paths. Default behavior is not to listen on any path. If relative paths are specified, they are interpreted relative to the network data directory. If paths include any parent directory components and the parent directories do not exist, they will be created. Enabling this gives local processes that can access the socket unauthenticated RPC access, so it's important to choose a path with secure permissions if customizing this.", ArgsManager::ALLOW_ANY, OptionsCategory::IPC);
}
#if HAVE_DECL_FORK
diff --git a/src/init/bitcoin-gui.cpp b/src/init/bitcoin-gui.cpp
index ca3077b9..02e8f063 100644
--- a/src/init/bitcoin-gui.cpp
+++ b/src/init/bitcoin-gui.cpp
@@ -8,6 +8,7 @@
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/node.h>
+#include <interfaces/rpc.h>
#include <interfaces/wallet.h>
#include <node/context.h>
#include <util/check.h>
@@ -33,6 +34,7 @@ public:
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
+ std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
interfaces::Ipc* ipc() override { return m_ipc.get(); }
// bitcoin-gui accepts -ipcbind option even though it does not use it
// directly. It just returns true here to accept the option because
diff --git a/src/init/bitcoin-node.cpp b/src/init/bitcoin-node.cpp
index e4252193..b4e5b8ee 100644
--- a/src/init/bitcoin-node.cpp
+++ b/src/init/bitcoin-node.cpp
@@ -8,6 +8,7 @@
#include <interfaces/init.h>
#include <interfaces/ipc.h>
#include <interfaces/node.h>
+#include <interfaces/rpc.h>
#include <interfaces/wallet.h>
#include <node/context.h>
#include <util/check.h>
@@ -36,6 +37,7 @@ public:
return MakeWalletLoader(chain, *Assert(m_node.args));
}
std::unique_ptr<interfaces::Echo> makeEcho() override { return interfaces::MakeEcho(); }
+ std::unique_ptr<interfaces::Rpc> makeRpc() override { return interfaces::MakeRpc(m_node); }
interfaces::Ipc* ipc() override { return m_ipc.get(); }
bool canListenIpc() override { return true; }
const char* exeName() override { return EXE_NAME; }
diff --git a/src/interfaces/README.md b/src/interfaces/README.md
index 97167d52..cbb2c40f 100644
--- a/src/interfaces/README.md
+++ b/src/interfaces/README.md
@@ -16,4 +16,6 @@ The following interfaces are defined here:
* [`Ipc`](ipc.h) — used by multiprocess code to access `Init` interface across processes. Added in [#19160](https://github.com/bitcoin/bitcoin/pull/19160).
+* [`Rpc`](rpc.h) — used by `bitcoin-cli` to be able to call RPC methods over a unix socket instead of TCP.
+
The interfaces above define boundaries between major components of bitcoin code (node, wallet, and gui), making it possible for them to run in [different processes](../../doc/multiprocess.md), and be tested, developed, and understood independently. These interfaces are not currently designed to be stable or to be used externally.
diff --git a/src/interfaces/init.h b/src/interfaces/init.h
index f214b287..d5b394b0 100644
--- a/src/interfaces/init.h
+++ b/src/interfaces/init.h
@@ -9,6 +9,7 @@
#include <interfaces/echo.h>
#include <interfaces/mining.h>
#include <interfaces/node.h>
+#include <interfaces/rpc.h>
#include <interfaces/wallet.h>
#include <memory>
@@ -36,6 +37,7 @@ public:
virtual std::unique_ptr<Mining> makeMining() { return nullptr; }
virtual std::unique_ptr<WalletLoader> makeWalletLoader(Chain& chain) { return nullptr; }
virtual std::unique_ptr<Echo> makeEcho() { return nullptr; }
+ virtual std::unique_ptr<Rpc> makeRpc() { return nullptr; }
virtual Ipc* ipc() { return nullptr; }
virtual bool canListenIpc() { return false; }
virtual const char* exeName() { return nullptr; }
diff --git a/src/interfaces/rpc.h b/src/interfaces/rpc.h
new file mode 100644
index 00000000..d8d5566a
--- /dev/null
+++ b/src/interfaces/rpc.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2025 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_INTERFACES_RPC_H
+#define BITCOIN_INTERFACES_RPC_H
+
+#include <memory>
+#include <string>
+
+class UniValue;
+
+namespace node {
+struct NodeContext;
+} // namespace node
+
+namespace interfaces {
+//! Interface giving clients ability to emulate HTTP RPC calls.
+class Rpc
+{
+public:
+ virtual ~Rpc() = default;
+ virtual UniValue executeRpc(UniValue request, std::string url, std::string user) = 0;
+};
+
+//! Return implementation of Rpc interface.
+std::unique_ptr<Rpc> MakeRpc(node::NodeContext& node);
+
+} // namespace interfaces
+
+#endif // BITCOIN_INTERFACES_RPC_H
diff --git a/src/ipc/CMakeLists.txt b/src/ipc/CMakeLists.txt
index 5378ef19..8326423d 100644
--- a/src/ipc/CMakeLists.txt
+++ b/src/ipc/CMakeLists.txt
@@ -14,6 +14,7 @@ target_capnp_sources(bitcoin_ipc ${CMAKE_CURRENT_SOURCE_DIR}
capnp/echo.capnp
capnp/init.capnp
capnp/mining.capnp
+ capnp/rpc.capnp
)
target_link_libraries(bitcoin_ipc
diff --git a/src/ipc/capnp/init-types.h b/src/ipc/capnp/init-types.h
index 2abd7b21..c6764d2a 100644
--- a/src/ipc/capnp/init-types.h
+++ b/src/ipc/capnp/init-types.h
@@ -7,5 +7,6 @@
#include <ipc/capnp/echo.capnp.proxy-types.h>
#include <ipc/capnp/mining.capnp.proxy-types.h>
+#include <ipc/capnp/rpc.capnp.proxy-types.h>
#endif // BITCOIN_IPC_CAPNP_INIT_TYPES_H
diff --git a/src/ipc/capnp/init.capnp b/src/ipc/capnp/init.capnp
index a20ef2fc..d95bfb74 100644
--- a/src/ipc/capnp/init.capnp
+++ b/src/ipc/capnp/init.capnp
@@ -15,11 +15,13 @@ $Proxy.includeTypes("ipc/capnp/init-types.h");
using Echo = import "echo.capnp";
using Mining = import "mining.capnp";
+using Rpc = import "rpc.capnp";
interface Init $Proxy.wrap("interfaces::Init") {
construct @0 (threadMap: Proxy.ThreadMap) -> (threadMap :Proxy.ThreadMap);
makeEcho @1 (context :Proxy.Context) -> (result :Echo.Echo);
makeMining @3 (context :Proxy.Context) -> (result :Mining.Mining);
+ makeRpc @4 (context :Proxy.Context) -> (result :Rpc.Rpc);
# DEPRECATED: no longer supported; server returns an error.
makeMiningOld2 @2 () -> ();
diff --git a/src/ipc/capnp/rpc-types.h b/src/ipc/capnp/rpc-types.h
new file mode 100644
index 00000000..4d385dee
--- /dev/null
+++ b/src/ipc/capnp/rpc-types.h
@@ -0,0 +1,12 @@
+// Copyright (c) 2025 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#ifndef BITCOIN_IPC_CAPNP_RPC_TYPES_H
+#define BITCOIN_IPC_CAPNP_RPC_TYPES_H
+
+#include <ipc/capnp/common.capnp.proxy-types.h>
+#include <ipc/capnp/common-types.h>
+#include <ipc/capnp/rpc.capnp.proxy.h>
+
+#endif // BITCOIN_IPC_CAPNP_RPC_TYPES_H
diff --git a/src/ipc/capnp/rpc.capnp b/src/ipc/capnp/rpc.capnp
new file mode 100644
index 00000000..c831424a
--- /dev/null
+++ b/src/ipc/capnp/rpc.capnp
@@ -0,0 +1,17 @@
+# Copyright (c) 2025 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+@0x9c3505dc45e146ac;
+
+using Cxx = import "/capnp/c++.capnp";
+$Cxx.namespace("ipc::capnp::messages");
+
+using Common = import "common.capnp";
+using Proxy = import "/mp/proxy.capnp";
+$Proxy.include("interfaces/rpc.h");
+$Proxy.includeTypes("ipc/capnp/rpc-types.h");
+
+interface Rpc $Proxy.wrap("interfaces::Rpc") {
+ executeRpc @0 (context :Proxy.Context, request :Text, uri :Text, user :Text) -> (result :Text);
+}
diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp
index 6c61b210..f806c40e 100644
--- a/src/node/interfaces.cpp
+++ b/src/node/interfaces.cpp
@@ -12,12 +12,14 @@
#include <consensus/validation.h>
#include <deploymentstatus.h>
#include <external_signer.h>
+#include <httprpc.h>
#include <index/blockfilterindex.h>
#include <init.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <interfaces/mining.h>
#include <interfaces/node.h>
+#include <interfaces/rpc.h>
#include <interfaces/types.h>
#include <interfaces/wallet.h>
#include <kernel/chain.h>
@@ -81,6 +83,7 @@ using interfaces::Handler;
using interfaces::MakeSignalHandler;
using interfaces::Mining;
using interfaces::Node;
+using interfaces::Rpc;
using interfaces::WalletLoader;
using kernel::ChainstateRole;
using node::BlockAssembler;
@@ -1011,6 +1014,24 @@ public:
bool m_interrupt_mining{false};
NodeContext& m_node;
};
+
+class RpcImpl : public Rpc
+{
+public:
+ explicit RpcImpl(NodeContext& node) : m_node(node) {}
+
+ UniValue executeRpc(UniValue request, std::string uri, std::string user) override
+ {
+ JSONRPCRequest req;
+ req.context = &m_node;
+ req.URI = std::move(uri);
+ req.authUser = std::move(user);
+ HTTPStatusCode status;
+ return ExecuteHTTPRPC(request, req, status);
+ }
+
+ NodeContext& m_node;
+};
} // namespace
} // namespace node
@@ -1030,4 +1051,5 @@ std::unique_ptr<Mining> MakeMining(node::NodeContext& context, bool wait_loaded)
}
return std::make_unique<node::MinerImpl>(context);
}
+std::unique_ptr<Rpc> MakeRpc(node::NodeContext& context) { return std::make_unique<node::RpcImpl>(context); }
} // namespace interfaces
Why this scored 35/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.