fix(lite-backend): verify TLS for the t-address scan and mempool monitor

The transparent-address txid scan in the sync loop and the mempool
monitor both hard-coded no_cert = true, so they connected to the
lightwalletd through get_client's NoCertificateVerification path in
every Lite build, while the app always passes dangerous = false. Block
sync, info, broadcast and the full-tx fetcher already honour
LightClientConfig::no_cert_verification. An active man in the middle on
those two channels could hide or invent transparent balance, UTXOs and
pending transactions (signing stays local, so no funds can be taken).

Both calls now take the setting from the config:
- the sync loop passes self.config.no_cert_verification to
  scan_taddress_txids;
- start_mempool_monitor reads config.no_cert_verification once and
  passes it to monitor_mempool.

Flagged behaviour change: a lightwalletd with an invalid or self-signed
certificate now fails the t-address stage (do_sync retries, then returns
the error) and the mempool monitor (it logs and retries), as block sync
already does. `dangerous` is the only bypass. No ABI, symbol or wallet
format change.

A tripwire test checks that lightclient.rs never again hard-codes the
bypass for either call; its needles are joined at runtime so the test
cannot match its own source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 13:22:00 -05:00
co-authored by Claude Opus 5.5
parent 1848025a95
commit 0a72b47ca6
@@ -1147,6 +1147,8 @@ impl LightClient {
pub fn start_mempool_monitor(lc: Arc<LightClient>) -> Result<(), String> { pub fn start_mempool_monitor(lc: Arc<LightClient>) -> Result<(), String> {
let config = lc.config.clone(); let config = lc.config.clone();
let uri = config.server.clone(); let uri = config.server.clone();
// TLS verification follows the config (default on), as block sync does; `dangerous` is the only bypass.
let no_cert = config.no_cert_verification;
let shutdown = lc.shutdown_flag.clone(); let shutdown = lc.shutdown_flag.clone();
let (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::<RawTransaction>(); let (incoming_mempool_tx, incoming_mempool_rx) = std::sync::mpsc::channel::<RawTransaction>();
@@ -1182,7 +1184,7 @@ pub fn start_mempool_monitor(lc: Arc<LightClient>) -> Result<(), String> {
incoming_mempool_tx_clone.send(rtx).map_err(|e| Box::new(e) as Box<dyn std::error::Error>) incoming_mempool_tx_clone.send(rtx).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}; };
match grpcconnector::monitor_mempool(&uri.clone(), true, send_closure).await { match grpcconnector::monitor_mempool(&uri.clone(), no_cert, send_closure).await {
Ok(_) => info!("Mempool monitor loop successful"), Ok(_) => info!("Mempool monitor loop successful"),
Err(e) => { Err(e) => {
// If the server doesn't implement GetMempoolStream, stop the monitor instead // If the server doesn't implement GetMempoolStream, stop the monitor instead
@@ -1558,10 +1560,9 @@ pub fn start_mempool_monitor(lc: Arc<LightClient>) -> Result<(), String> {
start_height start_height
}; };
let no_cert = true; // Fetch the txids our transparent addresses are involved with. TLS verification follows the
// config (default on); `dangerous` is the only way to turn it off.
// We'll also fetch all the txids that our transparent addresses are involved with self.scan_taddress_txids(&pool, block_times, transparent_start_height, end_height, self.config.no_cert_verification)?;
self.scan_taddress_txids(&pool, block_times, transparent_start_height, end_height, no_cert)?;
// Do block height accounting // Do block height accounting
last_scanned_height = end_height; last_scanned_height = end_height;
@@ -1905,4 +1906,16 @@ pub fn test_bad_import() {
} }
} }
// C243 tripwire: the t-address scan and the mempool monitor must take TLS verification from the config
// (no_cert_verification, which only `dangerous` sets), never from a hard-coded `true`. The needles are
// joined at runtime so that this test's own source cannot match them.
#[test]
pub fn test_tls_verification_not_hardcoded_off() {
let src = include_str!("lightclient.rs");
let taddr_needle = ["let no_cert", "= true"].join(" ");
let mempool_needle = ["monitor_mempool(&uri.clone(),", "true"].join(" ");
assert!(!src.contains(&taddr_needle), "the t-address scan turns TLS verification off");
assert!(!src.contains(&mempool_needle), "the mempool monitor turns TLS verification off");
}
} }