updater: use proxy for signed hash downloads
What changed, and why it matters
This change fixes a privacy leak in the Monero GUI wallet's update checker. Previously, when the wallet checked for updates and downloaded the signed list of official file hashes, it did not route that request through the user's configured proxy. This could expose the user's real IP address to the update server and any network observer, even if the user had set a proxy for other wallet traffic. The patch now passes the configured proxy address through to the update check so those hash downloads also go through the proxy.
Verify that Network::get actually applies m_proxyAddress to the HTTP request (e.g., via QNetworkProxy) and that no other update-related network paths bypass the proxy. Consider adding regression tests for proxy routing of update checks. Users who rely on proxies for privacy should upgrade to a release containing this fix.
Security signals we found
Privacy leak: update metadata fetch bypassed user-configured proxy
Proxy setting now propagated to signed hash download path
Potential deanonymization of users who rely on proxy for network privacy
Fix is partial/wiring-only; underlying proxy enforcement in Network not visible in diff
Evidence from the diff
The commit threads the user’s proxy setting from QML persistentSettings.getProxyAddress() through WalletManager::checkUpdatesAsync() into Updater::fetchSignedHash(), which now constructs Network with that proxy address. Network gains a new constructor accepting a proxyAddress and stores it in m_proxyAddress. The actual proxy application logic in Network::get or the HTTP client is not shown in this diff, so the patch is a wiring change that relies on pre-existing or follow-up proxy handling in the Network class. The security relevance is that update/hash fetches previously bypassed the proxy, leaking source IP and correlating wallet/update activity.
Changed components
main.qml update check invocationsrc/libwalletqt/WalletManager (checkUpdatesAsync signature and call)src/qt/updater (Updater::fetchSignedHash signature and Network construction)src/qt/network (new proxy-aware constructor)Inspect captured patch +19 / −6
diff --git a/main.qml b/main.qml
index ea473de..bc77b8d 100644
--- a/main.qml
+++ b/main.qml
@@ -2285,7 +2285,8 @@ ApplicationWindow {
function checkUpdates() {
const version = Version.GUI_VERSION.match(/\d+\.\d+\.\d+\.\d+/);
if (version) {
- walletManager.checkUpdatesAsync("monero-gui", "gui", getBuildTag(), version[0]);
+ walletManager.checkUpdatesAsync(
+ "monero-gui", "gui", getBuildTag(), version[0], persistentSettings.getProxyAddress());
} else {
console.error("failed to parse version number", Version.GUI_VERSION);
}
diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp
index 73046af..3d01f9f 100644
--- a/src/libwalletqt/WalletManager.cpp
+++ b/src/libwalletqt/WalletManager.cpp
@@ -502,9 +502,10 @@ void WalletManager::checkUpdatesAsync(
const QString &software,
const QString &subdir,
const QString &buildTag,
- const QString &version)
+ const QString &version,
+ const QString &proxyAddress)
{
- m_scheduler.run([this, software, subdir, buildTag, version] {
+ m_scheduler.run([this, software, subdir, buildTag, version, proxyAddress] {
const auto updateInfo = Monero::WalletManager::checkUpdates(
software.toStdString(),
subdir.toStdString(),
@@ -523,7 +524,8 @@ void WalletManager::checkUpdatesAsync(
{
const QString binaryFilename = QUrl(downloadUrl).fileName();
QPair<QString, QString> signers;
- const QString signedHash = Updater().fetchSignedHash(binaryFilename, hashFromDns, signers).toHex();
+ const QString signedHash =
+ Updater().fetchSignedHash(binaryFilename, hashFromDns, proxyAddress, signers).toHex();
qInfo() << "Update found" << version << downloadUrl << "hash" << signedHash << "signed by" << signers;
emit checkUpdatesComplete(version, downloadUrl, signedHash, signers.first, signers.second);
diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h
index 649a9f9..aada9bd 100644
--- a/src/libwalletqt/WalletManager.h
+++ b/src/libwalletqt/WalletManager.h
@@ -186,7 +186,8 @@ public:
const QString &software,
const QString &subdir,
const QString &buildTag,
- const QString &version);
+ const QString &version,
+ const QString &proxyAddress);
Q_INVOKABLE QString checkUpdates(const QString &software, const QString &subdir) const;
// clear/rename wallet cache
diff --git a/src/qt/network.cpp b/src/qt/network.cpp
index 574369c..fc9a884 100644
--- a/src/qt/network.cpp
+++ b/src/qt/network.cpp
@@ -95,7 +95,13 @@ bool HttpClient::handle_target_data(std::string &piece_of_transfer)
}
Network::Network(QObject *parent)
+ : Network({}, parent)
+{
+}
+
+Network::Network(const QString &proxyAddress, QObject *parent)
: QObject(parent)
+ , m_proxyAddress(proxyAddress)
, m_scheduler(this)
{
}
diff --git a/src/qt/network.h b/src/qt/network.h
index 70f1719..57e6c9b 100644
--- a/src/qt/network.h
+++ b/src/qt/network.h
@@ -74,6 +74,7 @@ class Network : public QObject
public:
Network(QObject *parent = nullptr);
+ Network(const QString &proxyAddress, QObject *parent = nullptr);
public:
Q_INVOKABLE void get(const QString &url, const QJSValue &callback, const QString &contentType = {}) const;
diff --git a/src/qt/updater.cpp b/src/qt/updater.cpp
index 14fc87e..0d762be 100644
--- a/src/qt/updater.cpp
+++ b/src/qt/updater.cpp
@@ -49,12 +49,13 @@ Updater::Updater()
QByteArray Updater::fetchSignedHash(
const QString &binaryFilename,
const QByteArray &hashFromDns,
+ const QString &proxyAddress,
QPair<QString, QString> &signers) const
{
static constexpr const char hashesTxtUrl[] = "https://web.getmonero.org/downloads/hashes.txt";
static constexpr const char hashesTxtSigUrl[] = "https://web.getmonero.org/downloads/hashes.txt.sig";
- const Network network;
+ const Network network(proxyAddress);
std::string hashesTxt = network.get(hashesTxtUrl);
std::string hashesTxtSig = network.get(hashesTxtSigUrl);
diff --git a/src/qt/updater.h b/src/qt/updater.h
index 3cbb618..6b69076 100644
--- a/src/qt/updater.h
+++ b/src/qt/updater.h
@@ -40,6 +40,7 @@ public:
QByteArray fetchSignedHash(
const QString &binaryFilename,
const QByteArray &hashFromDns,
+ const QString &proxyAddress,
QPair<QString, QString> &signers) const;
QByteArray getHash(const void *data, size_t size) const;
QPair<QString, QString> verifySignaturesAndHashSum(
Why this scored 59/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.