wallet: unrestrict `get_transfers` and `get_transfer_by_txid`
What changed, and why it matters
This change makes two wallet RPC commands, `get_transfers` and `get_transfer_by_txid`, usable in 'restricted' mode, which previously blocked them entirely. In restricted mode, these commands can now read confirmed and historical wallet transaction data, but they still cannot refresh the mempool/pool state. The change is described by the project as an intentional relaxation of restrictions, not as a security fix. It may expose more wallet history to anyone who can reach a restricted wallet RPC, but it does not by itself bypass authentication or steal funds.
Operators running restricted wallet RPC servers should review whether exposing `get_transfers` and `get_transfer_by_txid` to restricted clients is acceptable for their threat model; if not, downgrade or add an additional access-control layer. No immediate patch for a vulnerability is required, but monitor release notes for any further hardening of restricted-mode defaults.
Security signals we found
Relaxation of an access-control restriction (restricted-mode RPC allowlist)
Exposure of wallet transaction history and payment metadata to restricted RPC clients
Mempool refresh side effects still gated behind `!m_restricted`
No authentication bypass, code injection, or cryptographic weakness in the diff
Evidence from the diff
The commit removes the top-level m_restricted denial for on_get_transfers and on_get_transfer_by_txid in src/wallet/wallet_rpc_server.cpp. It wraps only the update_pool_state/process_pool_state calls in !m_restricted guards, while leaving the rest of the query logic (confirmed transfers, unconfirmed payments list, transfer lookup by txid) unrestricted. Helper fill_transfer_entry overloads are marked const to allow calling from const contexts. The practical effect is that a restricted wallet RPC server now serves wallet transfer history and pool-payment lists, whereas before it refused the whole command.
Changed components
Monero wallet RPC server (`src/wallet/wallet_rpc_server.cpp`)Monero wallet RPC server header (`src/wallet/wallet_rpc_server.h`)RPC commands `get_transfers` and `get_transfer_by_txid`Inspect captured patch +22 / −28
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index 7761348..5383aae 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -473,7 +473,7 @@ namespace tools
return false;
}
//------------------------------------------------------------------------------------------------------------------------------
- void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd)
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) const
{
entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
entry.payment_id = string_tools::pod_to_hex(payment_id);
@@ -494,7 +494,7 @@ namespace tools
set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward(), pd.m_unlock_time);
}
//------------------------------------------------------------------------------------------------------------------------------
- void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd)
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) const
{
entry.txid = string_tools::pod_to_hex(txid);
entry.payment_id = string_tools::pod_to_hex(pd.m_payment_id);
@@ -524,7 +524,7 @@ namespace tools
set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward(), pd.m_unlock_time);
}
//------------------------------------------------------------------------------------------------------------------------------
- void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd)
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) const
{
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
entry.txid = string_tools::pod_to_hex(txid);
@@ -554,7 +554,7 @@ namespace tools
set_confirmations(entry, m_wallet->get_blockchain_current_height(), m_wallet->get_last_block_reward(), pd.m_tx.unlock_time);
}
//------------------------------------------------------------------------------------------------------------------------------
- void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd)
+ void wallet_rpc_server::fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &ppd) const
{
const tools::wallet2::payment_details &pd = ppd.m_pd;
entry.txid = string_tools::pod_to_hex(pd.m_tx_hash);
@@ -2909,12 +2909,6 @@ namespace tools
bool wallet_rpc_server::on_get_transfers(const wallet_rpc::COMMAND_RPC_GET_TRANSFERS::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFERS::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
- if (m_restricted)
- {
- er.code = WALLET_RPC_ERROR_CODE_DENIED;
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
uint64_t min_height = 0, max_height = CRYPTONOTE_MAX_BLOCK_NUMBER;
if (req.filter_by_height)
@@ -2967,10 +2961,13 @@ namespace tools
if (req.pool)
{
- std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs;
- m_wallet->update_pool_state(process_txs);
- if (!process_txs.empty())
- m_wallet->process_pool_state(process_txs);
+ if (!m_restricted)
+ {
+ std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs;
+ m_wallet->update_pool_state(process_txs);
+ if (!process_txs.empty())
+ m_wallet->process_pool_state(process_txs);
+ }
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> payments;
m_wallet->get_unconfirmed_payments(payments, account_index, subaddr_indices);
@@ -2986,12 +2983,6 @@ namespace tools
bool wallet_rpc_server::on_get_transfer_by_txid(const wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::request& req, wallet_rpc::COMMAND_RPC_GET_TRANSFER_BY_TXID::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
- if (m_restricted)
- {
- er.code = WALLET_RPC_ERROR_CODE_DENIED;
- er.message = "Command unavailable in restricted mode.";
- return false;
- }
crypto::hash txid;
cryptonote::blobdata txid_blob;
@@ -3050,10 +3041,13 @@ namespace tools
}
}
- std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs;
- m_wallet->update_pool_state(process_txs);
- if (!process_txs.empty())
- m_wallet->process_pool_state(process_txs);
+ if (!m_restricted)
+ {
+ std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs;
+ m_wallet->update_pool_state(process_txs);
+ if (!process_txs.empty())
+ m_wallet->process_pool_state(process_txs);
+ }
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> pool_payments;
m_wallet->get_unconfirmed_payments(pool_payments, req.account_index);
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index 742e50d..3f53044 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -268,10 +268,10 @@ namespace tools
bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
// helpers
- void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd);
- void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd);
- void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd);
- void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd);
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const crypto::hash &payment_id, const tools::wallet2::payment_details &pd) const;
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::confirmed_transfer_details &pd) const;
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &txid, const tools::wallet2::unconfirmed_transfer_details &pd) const;
+ void fill_transfer_entry(tools::wallet_rpc::transfer_entry &entry, const crypto::hash &payment_id, const tools::wallet2::pool_payment_details &pd) const;
bool not_open(epee::json_rpc::error& er);
void handle_rpc_exception(const std::exception_ptr& e, epee::json_rpc::error& er, int default_error_code);
Why this scored 34/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.