fix: security-audit remediation (15 findings), empty-wallet warning, and send/chat/console/shutdown UX

Security audit remediation (15 confirmed findings from the codebase audit):
- H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths
  (RAII guard) and purge stale obsidiandecryptexport* files at startup.
- M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker
  passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported
  key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and
  the first-run wizard "Skip" buffers.
- M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon
  getters (dedicated error_mutex_; DaemonController::lastError now by value),
  route xmrig last_error_ writes through a locked setter, and wrap
  shutdown_status_/wizard_stop_status_ in a locking GuardedStatus
  (wizard_stopping_external_ -> std::atomic).
- M-02: persist after a console send/shield/import in the lite backend.
- L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress.
- L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules.
- L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs.
- I-01: extract updater archives from the already-verified in-memory buffer
  (no disk re-read TOCTOU).

Feature: warn once (full-node) when the active wallet loads empty while a sibling
wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to
the recovery/Restore flow; a funded sibling .dat routes to the wallet manager.
Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false
positives on warm reconnect / spent-down wallets.

UX fixes:
- send: show the TOTAL balance (with a spendable "available" note) in the source
  dropdown and keep pending-change addresses visible.
- chat: insert emoji at the cursor position; restrict new-chat recipients to
  shielded (z) addresses.
- console: optional auto-focus of the command input on tab open (off by default).
- shutdown: when "stop external daemon" is on, keep the shutdown screen up until
  the external node actually exits, showing live status.

Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 13:18:49 -05:00
co-authored by Claude Opus 4.8
parent ea26c0cbbb
commit 6ee81a5abe
40 changed files with 745 additions and 90 deletions
+22 -7
View File
@@ -224,12 +224,11 @@ std::vector<std::string> EmbeddedDaemon::getChainParams()
void EmbeddedDaemon::setState(State s, const std::string& message)
{
state_ = s;
if (!message.empty()) {
if (s == State::Error) {
last_error_ = message;
}
if (!message.empty() && s == State::Error) {
std::lock_guard<std::mutex> lk(error_mutex_); // dedicated mutex — never taken with output_mutex_ held
last_error_ = message;
}
if (state_callback_) {
state_callback_(s, message);
}
@@ -621,12 +620,28 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
// Forward declaration — defined after startProcess
static DWORD findProcessByName(const char* name);
// Quote a single argument per the CommandLineToArgvW rules (MSDN) so a value containing a space or a
// quote is delivered as ONE argv token to the daemon instead of splitting/corrupting argv (L-02).
static std::string quoteWinArg(const std::string& arg) {
if (!arg.empty() && arg.find_first_of(" \t\n\v\"") == std::string::npos) return arg;
std::string out = "\"";
for (size_t i = 0; ; ++i) {
size_t nbs = 0;
while (i < arg.size() && arg[i] == '\\') { ++nbs; ++i; }
if (i == arg.size()) { out.append(nbs * 2, '\\'); break; }
if (arg[i] == '"') { out.append(nbs * 2 + 1, '\\'); out.push_back('"'); }
else { out.append(nbs, '\\'); out.push_back(arg[i]); }
}
out.push_back('"');
return out;
}
bool EmbeddedDaemon::startProcess(const std::string& binary_path, const std::vector<std::string>& args)
{
// Build command line
// Build command line (binary path always quoted; each arg quoted/escaped per Windows rules — L-02)
std::string cmd = "\"" + binary_path + "\"";
for (const auto& arg : args) {
cmd += " " + arg;
cmd += " " + quoteWinArg(arg);
}
DEBUG_LOGF("[INFO] Starting daemon: %s\n", cmd.c_str());