What changed, and why it matters
This commit fixes a privacy and security bug where Monero wallets ignored the user's --no-dns setting and still performed DNS lookups for OpenAlias addresses (human-readable names like donate.getmonero.org). Now, when DNS is disabled or the wallet is offline, the wallet will not make those DNS queries. This prevents accidental network leaks and reduces the chance that a malicious or monitored DNS server could manipulate address resolution.
Users who rely on --no-dns or offline mode for privacy should upgrade to a release containing this commit. Operators of wallet RPC services should review whether they expose OpenAlias resolution and ensure the no-dns/offline setting is respected.
Security signals we found
Fixes a configuration-bypass bug where --no-dns did not prevent OpenAlias DNS lookups
Prevents unintended outbound DNS queries that could leak user intent or metadata
Reduces attack surface for DNS-based address spoofing when the user explicitly disabled DNS
Adds unit test coverage for the disabled/offline DNS path
Evidence from the diff
The patch adds an allow_dns parameter to cryptonote::get_account_address_from_str_or_url(). If allow_dns is false, the function returns false immediately after failing to parse a raw address, skipping the OpenAlias DNS lookup. The wallet’s is_dns_enabled() helper returns m_use_dns && !m_offline. Both simplewallet and the wallet RPC server now pass m_wallet->is_dns_enabled() into address resolution calls. A unit test verifies that DNS resolution is skipped when DNS is disabled or the wallet is offline.
Changed components
src/cryptonote_basic/cryptonote_basic_impl.cppsrc/cryptonote_basic/cryptonote_basic_impl.hsrc/simplewallet/simplewallet.cppsrc/wallet/wallet2.hsrc/wallet/wallet_rpc_server.cpptests/unit_tests/address_from_url.cppInspect captured patch +55 / −17
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp
index 3b5f2ef..7ba824e 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.cpp
+++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp
@@ -236,9 +236,22 @@ namespace cryptonote {
, const std::string& str_or_url
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
)
+ {
+ return get_account_address_from_str_or_url(info, nettype, str_or_url, true, dns_confirm);
+ }
+ //--------------------------------------------------------------------------------
+ bool get_account_address_from_str_or_url(
+ address_parse_info& info
+ , network_type nettype
+ , const std::string& str_or_url
+ , bool allow_dns
+ , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
+ )
{
if (get_account_address_from_str(info, nettype, str_or_url))
return true;
+ if (!allow_dns)
+ return false;
bool dnssec_valid;
std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm);
return !address_str.empty() &&
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h
index ff7c59c..15686f4 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.h
+++ b/src/cryptonote_basic/cryptonote_basic_impl.h
@@ -90,6 +90,14 @@ namespace cryptonote {
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
);
+ bool get_account_address_from_str_or_url(
+ address_parse_info& info
+ , network_type nettype
+ , const std::string& str_or_url
+ , bool allow_dns
+ , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
+ );
+
bool is_coinbase(const transaction_prefix& tx);
bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b);
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 066c1d1..39fe5cb 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -6418,7 +6418,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
bool has_uri = m_wallet->parse_uri(local_args[i], address_uri, payment_id_uri, amount, tx_description, recipient_name, unknown_parameters, error);
if (has_uri)
{
- r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), address_uri, oa_prompter);
+ r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), address_uri, m_wallet->is_dns_enabled(), oa_prompter);
if (payment_id_uri.size() == 16)
{
if (!tools::wallet2::parse_short_payment_id(payment_id_uri, info.payment_id))
@@ -6434,7 +6434,7 @@ bool simple_wallet::transfer_main(const std::vector<std::string> &args_, bool ca
}
else if (i + 1 < local_args.size())
{
- r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[i], oa_prompter);
+ r = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[i], m_wallet->is_dns_enabled(), oa_prompter);
bool ok = cryptonote::parse_amount(de.amount, local_args[i + 1]);
if(!ok || 0 == de.amount)
{
@@ -6961,7 +6961,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, const std::vect
}
cryptonote::address_parse_info info;
- if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[0], oa_prompter))
+ if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[0], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
print_usage();
@@ -7221,7 +7221,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_)
}
cryptonote::address_parse_info info;
- if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[1], oa_prompter))
+ if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[1], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -7801,7 +7801,7 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
if (local_args.size() > 1)
{
cryptonote::address_parse_info info;
- if (cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args.back(), oa_prompter))
+ if (cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args.back(), m_wallet->is_dns_enabled(), oa_prompter))
{
if (!info.is_subaddress)
{
@@ -7882,7 +7882,7 @@ bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
}
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -7950,7 +7950,7 @@ bool simple_wallet::check_tx_key(const std::vector<std::string> &args_)
}
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[2], oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), local_args[2], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -8014,7 +8014,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args)
// parse address
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -8220,7 +8220,7 @@ bool simple_wallet::check_reserve_proof(const std::vector<std::string> &args)
return true;
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[0], oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[0], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -9514,7 +9514,7 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v
else if (args[0] == "add")
{
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[1], m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -9794,7 +9794,7 @@ bool simple_wallet::verify(const std::vector<std::string> &args)
}
cryptonote::address_parse_info info;
- if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), address_string, oa_prompter))
+ if(!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), address_string, m_wallet->is_dns_enabled(), oa_prompter))
{
fail_msg_writer() << tr("failed to parse address");
return true;
@@ -10720,7 +10720,7 @@ void simple_wallet::mms_signer(const std::vector<std::string> &args)
if (args.size() == 4)
{
cryptonote::address_parse_info info;
- bool ok = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[3], oa_prompter);
+ bool ok = cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), args[3], m_wallet->is_dns_enabled(), oa_prompter);
if (!ok)
{
fail_msg_writer() << tr("Invalid Monero address");
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index e310d64..b647589 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -1481,6 +1481,7 @@ private:
uint64_t hash_m_transfers(boost::optional<uint64_t> transfer_height, crypto::hash &hash) const;
void finish_rescan_bc_keep_key_images(uint64_t transfer_height, const crypto::hash &hash);
void enable_dns(bool enable) { m_use_dns = enable; }
+ bool is_dns_enabled() const { return m_use_dns && !m_offline; }
void set_offline(bool offline = true);
bool is_offline() const { return m_offline; }
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index e64df58..e5b912a 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -1047,7 +1047,7 @@ namespace tools
cryptonote::address_parse_info info;
cryptonote::tx_destination_entry de;
er.message = "";
- if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), it->address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), it->address, m_wallet->is_dns_enabled(),
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -2483,7 +2483,7 @@ namespace tools
cryptonote::address_parse_info info;
er.message = "";
- if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address, m_wallet->is_dns_enabled(),
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -3287,7 +3287,7 @@ namespace tools
cryptonote::address_parse_info info;
er.message = "";
- if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address, m_wallet->is_dns_enabled(),
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -3335,7 +3335,7 @@ namespace tools
if (req.set_address)
{
er.message = "";
- if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address,
+ if(!get_account_address_from_str_or_url(info, m_wallet->nettype(), req.address, m_wallet->is_dns_enabled(),
[&er](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
@@ -4735,7 +4735,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,
+ res.valid = get_account_address_from_str_or_url(info, net_type.type, req.address, !m_wallet || m_wallet->is_dns_enabled(),
[&er, &address](const std::string &url, const std::vector<std::string> &addresses, bool dnssec_valid)->std::string {
if (!dnssec_valid)
{
diff --git a/tests/unit_tests/address_from_url.cpp b/tests/unit_tests/address_from_url.cpp
index c510785..34fa34f 100644
--- a/tests/unit_tests/address_from_url.cpp
+++ b/tests/unit_tests/address_from_url.cpp
@@ -116,3 +116,19 @@ TEST(AddressFromURL, Failure)
ASSERT_EQ(0, addresses.size());
}
+
+TEST(AddressFromURL, Disabled)
+{
+ cryptonote::address_parse_info info;
+ tools::wallet2 wallet;
+ wallet.enable_dns(false);
+
+ EXPECT_TRUE(cryptonote::get_account_address_from_str_or_url(
+ info, cryptonote::MAINNET, MONERO_DONATION_ADDR, wallet.is_dns_enabled()));
+ EXPECT_FALSE(cryptonote::get_account_address_from_str_or_url(
+ info, cryptonote::MAINNET, "donate.getmonero.org", wallet.is_dns_enabled()));
+
+ wallet.enable_dns(true);
+ wallet.set_offline();
+ EXPECT_FALSE(wallet.is_dns_enabled());
+}
Why this scored 52/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.