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:
@@ -160,6 +160,20 @@ static bool IsValidTransparentAddr(const char* a) {
|
||||
return a && dragonx::util::isTransparentAddress(a);
|
||||
}
|
||||
|
||||
// Source-address balance caption for the "sending from" dropdown: the TOTAL balance is the headline (so
|
||||
// it matches the Overview figure and a source whose change is still confirming never looks like it lost
|
||||
// funds), with the confirmed-spendable amount shown as a smaller "(N available)" note ONLY when a pending
|
||||
// send/receive makes it differ. The Max button + validation still cap spends at the spendable amount.
|
||||
static std::string FormatSourceBalance(double total, double spendable) {
|
||||
char b[128];
|
||||
if (total - spendable > 1e-9)
|
||||
snprintf(b, sizeof(b), "%.8f %s (%.8f %s)", total, DRAGONX_TICKER,
|
||||
spendable, TR("send_available_note"));
|
||||
else
|
||||
snprintf(b, sizeof(b), "%.8f %s", total, DRAGONX_TICKER);
|
||||
return b;
|
||||
}
|
||||
|
||||
static std::string timeAgo(int64_t timestamp) {
|
||||
return dragonx::util::formatTimeAgoShort(timestamp);
|
||||
}
|
||||
@@ -253,8 +267,9 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
const char* tag = isZ ? "[Z]" : "[T]";
|
||||
std::string trunc = util::truncateMiddle(addr.address,
|
||||
static_cast<int>(std::max(S.drawElement("tabs.send", "addr-preview-trunc-min").size, width / S.drawElement("tabs.send", "addr-preview-trunc-divisor").size)));
|
||||
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
|
||||
tag, trunc.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
snprintf(buf, sizeof(buf), "%s %s — %s",
|
||||
tag, trunc.c_str(),
|
||||
FormatSourceBalance(addr.balance, addr.spendableBalance).c_str());
|
||||
s_source_preview = buf;
|
||||
} else {
|
||||
s_source_preview = TR("send_select_source");
|
||||
@@ -267,8 +282,17 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
if (!app->isConnected() || state.addresses.empty()) {
|
||||
ImGui::TextDisabled("%s", TR("no_addresses_available"));
|
||||
} else {
|
||||
// Sort by balance descending, only show spendable addresses with balance
|
||||
std::vector<size_t> sortedIdx = sortedSpendableAddressIndices(state.addresses);
|
||||
// List every address that HOLDS a balance (confirmed or still-confirming), sorted by total
|
||||
// descending — so a source whose change is pending stays visible with its real total instead
|
||||
// of vanishing. The spend gates (Max / validation) below still cap at the spendable amount.
|
||||
std::vector<size_t> sortedIdx;
|
||||
sortedIdx.reserve(state.addresses.size());
|
||||
for (size_t i = 0; i < state.addresses.size(); ++i)
|
||||
if (state.addresses[i].isSpendable() && state.addresses[i].balance > 0.0)
|
||||
sortedIdx.push_back(i);
|
||||
std::sort(sortedIdx.begin(), sortedIdx.end(), [&](size_t a, size_t b) {
|
||||
return state.addresses[a].balance > state.addresses[b].balance;
|
||||
});
|
||||
|
||||
if (sortedIdx.empty()) {
|
||||
ImGui::TextDisabled("%s", TR("send_no_balance"));
|
||||
@@ -283,8 +307,9 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
const char* tag = isZ ? "[Z]" : "[T]";
|
||||
|
||||
std::string trunc = util::truncateMiddle(addr.address, (int)addrTruncLen);
|
||||
snprintf(buf, sizeof(buf), "%s %s — %.8f %s",
|
||||
tag, trunc.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
snprintf(buf, sizeof(buf), "%s %s — %s",
|
||||
tag, trunc.c_str(),
|
||||
FormatSourceBalance(addr.balance, addr.spendableBalance).c_str());
|
||||
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
if (ImGui::Selectable(buf, isCurrent)) {
|
||||
@@ -293,8 +318,9 @@ static void RenderSourceDropdown(App* app, float width) {
|
||||
addr.address.c_str());
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
material::Tooltip("%s\nBalance: %.8f %s",
|
||||
addr.address.c_str(), addr.spendableBalance, DRAGONX_TICKER);
|
||||
material::Tooltip("%s\n%s",
|
||||
addr.address.c_str(),
|
||||
FormatSourceBalance(addr.balance, addr.spendableBalance).c_str());
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user