What changed, and why it matters
This patch fixes a privacy leak in Monero's wallet RPC server. Previously, the --no-dns flag was ignored when no wallet was loaded, so the validate_address RPC call could still perform a DNS lookup (OpenAlias) even though the user had explicitly disabled DNS. This could leak which address or alias a user was checking to DNS servers. The fix makes the RPC server honor --no-dns and --offline flags even without a wallet open.
Users running monero-wallet-rpc with --no-dns or --offline should upgrade to ensure DNS is not silently used for validate_address when no wallet is loaded. Review other RPC endpoints for similar wallet-state-dependent flag handling.
Security signals we found
Privacy leak: RPC ignored --no-dns when no wallet loaded
DNS lookup performed despite explicit user opt-out
OpenAlias resolution could disclose queried addresses/aliases to DNS resolvers
Fix adds command-line flag checks independent of wallet state
Evidence from the diff
The commit adds wallet2::has_offline_option() and wallet2::has_dns_option() helpers, then uses them in wallet_rpc_server::on_validate_address to compute allow_dns when m_wallet is null. Previously the code used !m_wallet || m_wallet->is_dns_enabled(), which defaulted to true (DNS allowed) when no wallet existed, bypassing –no-dns. The new logic: allow_dns = m_wallet ? m_wallet->is_dns_enabled() : wallet2::has_dns_option(m_vm) && !wallet2::has_offline_option(m_vm). Functional tests were updated to cover wallets with –no-dns and –offline.
Changed components
src/wallet/wallet2.cppsrc/wallet/wallet2.hsrc/wallet/wallet_rpc_server.cpptests/functional_tests/functional_tests_rpc.pytests/functional_tests/validate_address.pyInspect captured patch +33 / −2
### src/wallet/wallet2.cpp
@@ -1319,6 +1319,16 @@ bool wallet2::has_password_option(const boost::program_options::variables_map& v
return command_line::has_arg(vm, options().password);
}
+bool wallet2::has_offline_option(const boost::program_options::variables_map& vm)
+{
+ return command_line::get_arg(vm, options().offline);
+}
+
+bool wallet2::has_dns_option(const boost::program_options::variables_map& vm)
+{
+ return !command_line::get_arg(vm, options().no_dns);
+}
+
std::string wallet2::device_name_option(const boost::program_options::variables_map& vm)
{
return command_line::get_arg(vm, options().hw_device);
### src/wallet/wallet2.h
@@ -218,6 +218,8 @@ namespace tools
static bool has_testnet_option(const boost::program_options::variables_map& vm);
static bool has_stagenet_option(const boost::program_options::variables_map& vm);
static bool has_password_option(const boost::program_options::variables_map& vm);
+ static bool has_offline_option(const boost::program_options::variables_map& vm);
+ static bool has_dns_option(const boost::program_options::variables_map& vm);
static std::string device_name_option(const boost::program_options::variables_map& vm);
static std::string device_derivation_path_option(const boost::program_options::variables_map &vm);
static void init_options(boost::program_options::options_description& desc_params);
### src/wallet/wallet_rpc_server.cpp
@@ -4870,6 +4870,8 @@ namespace tools
bool wallet_rpc_server::on_validate_address(const wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_VALIDATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
cryptonote::address_parse_info info;
+ const bool allow_dns = m_wallet ? m_wallet->is_dns_enabled()
+ : wallet2::has_dns_option(*m_vm) && !wallet2::has_offline_option(*m_vm);
static const struct { cryptonote::network_type type; const char *stype; } net_types[] = {
{ cryptonote::MAINNET, "mainnet" },
{ cryptonote::TESTNET, "testnet" },
@@ -4883,7 +4885,7 @@ namespace tools
if (req.allow_openalias)
{
std::string address;
- res.valid = get_account_address_from_str_or_url(info, net_type.type, req.address, !m_wallet || m_wallet->is_dns_enabled(),
+ res.valid = get_account_address_from_str_or_url(info, net_type.type, req.address, allow_dns,
[&er, &address](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
### tests/functional_tests/functional_tests_rpc.py
@@ -67,7 +67,7 @@
["--daemon-port", "18180", "--disable-rpc-login"],
["--daemon-port", "18180", "--disable-rpc-login"],
["--daemon-port", "18180", "--disable-rpc-login"],
- ["--daemon-port", "18182", "--disable-rpc-login"],
+ ["--daemon-port", "18182", "--disable-rpc-login", "--no-dns"],
["--offline", "--disable-rpc-login"],
["--daemon-port", "18184", "--daemon-login", "md5_lover:Z1ON0101", "--rpc-login", "kyle:reveille"],
["--offline", "--disable-rpc-login", "--generate-from-json", WALLET_DIRECTORY + "/polyseed.json"],
### tests/functional_tests/validate_address.py
@@ -105,5 +105,22 @@ def check_openalias_addresses(self):
assert res.nettype == 'mainnet'
assert res.openalias_address == address[1]
+ for idx, expect_dns in [(0, True), (4, False), (5, False)]:
+ wallet = Wallet(idx = idx)
+ try: wallet.close_wallet()
+ except: pass
+ try:
+ wallet.get_address()
+ except AssertionError as e:
+ assert e.args[0]['error']['code'] == -13
+ else:
+ assert False
+ res = wallet.validate_address(address[0], any_net_type = True, allow_openalias = True)
+ assert res.valid == expect_dns
+ if expect_dns:
+ assert res.openalias_address == address[1]
+ res = wallet.validate_address(address[1], any_net_type = True, allow_openalias = True)
+ assert res.valid
+
if __name__ == '__main__':
AddressValidationTest().run_test()Why this scored 51/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.