fix(chat,rpc): address adversarial review of the chat backlog
Four confirmed findings from the review pass: SECURITY — B7 was incomplete: - The single-key Export dialog (key_export_dialog) still used plain call() for z_exportkey/dumpprivkey/z_exportviewingkey — a live spending-key leak on the most common per-address export path, missed by the B7 commit. - callSecret() zeros the raw body but the parsed json holds its OWN heap copy of the secret; several callers did .get<string>() on a temporary json and freed that copy un-wiped. Fix: add RPCClient::callSecretString() — returns the bare-string result with BOTH the raw body AND the json node zeroed, so callers can't forget. Route key_export_dialog (×2), exportPrivateKey, and export_all_keys (×2) through it; scrub the z_exportmnemonic json node in seed_wallet_creator (object result); also wipe the transient key copies, the displayed s_key on reset, and the aggregated export-all `keys` buffer. CHAT: - Jump-to-latest pill: SetCursorScreenPos moved the parent cursor and never restored it, so the composer footer rendered ~8px too high while scrolled up. Save + restore the cursor around the pill. - New-message toast: gating on a chatUnreadCount() watermark delta could be swallowed when an outgoing echo (wall-clock) pushed the seen-watermark past a later reply's block time. ingest() now reports the cids it appended; the toast fires when any is a non-muted conversation — skew-proof, still mute-aware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1689,11 +1689,14 @@ void App::refreshTransactionData()
|
||||
!result.hushChatMetadata.empty()) {
|
||||
std::unordered_map<std::string, std::int64_t> chatTxTimes;
|
||||
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
|
||||
const int unreadBefore = chatUnreadCount();
|
||||
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
// Toast only when a non-muted conversation gained an unread message — chatUnreadCount()
|
||||
// already skips muted cids, so a delta cleanly respects mute (Q10 + Q4).
|
||||
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat && chatUnreadCount() > unreadBefore)
|
||||
std::vector<std::string> newChatCids;
|
||||
chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr), &newChatCids);
|
||||
// Toast when a NON-muted conversation received a new message and we're off the Chat tab.
|
||||
// Gate on the ingest-reported cids, not a seen-watermark delta — the latter can be swallowed
|
||||
// by block-time vs wall-clock (echo) skew (Q10 + Q4).
|
||||
if (current_page_ != ui::NavPage::Chat &&
|
||||
std::any_of(newChatCids.begin(), newChatCids.end(),
|
||||
[this](const std::string& cid){ return !(settings_ && settings_->isChatMuted(cid)); }))
|
||||
ui::Notifications::instance().info(TR("chat_new_message_toast"));
|
||||
}
|
||||
NetworkRefreshService::applyTransactionRefreshResult(
|
||||
@@ -1752,11 +1755,14 @@ void App::refreshRecentTransactionData()
|
||||
!result.hushChatMetadata.empty()) {
|
||||
std::unordered_map<std::string, std::int64_t> chatTxTimes;
|
||||
for (const auto& tx : result.transactions) chatTxTimes[tx.txid] = tx.timestamp;
|
||||
const int unreadBefore = chatUnreadCount();
|
||||
const int newMsgs = chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr));
|
||||
// Toast only when a non-muted conversation gained an unread message — chatUnreadCount()
|
||||
// already skips muted cids, so a delta cleanly respects mute (Q10 + Q4).
|
||||
if (newMsgs > 0 && current_page_ != ui::NavPage::Chat && chatUnreadCount() > unreadBefore)
|
||||
std::vector<std::string> newChatCids;
|
||||
chat_service_.ingest(result.hushChatMetadata, chatTxTimes, std::time(nullptr), &newChatCids);
|
||||
// Toast when a NON-muted conversation received a new message and we're off the Chat tab.
|
||||
// Gate on the ingest-reported cids, not a seen-watermark delta — the latter can be swallowed
|
||||
// by block-time vs wall-clock (echo) skew (Q10 + Q4).
|
||||
if (current_page_ != ui::NavPage::Chat &&
|
||||
std::any_of(newChatCids.begin(), newChatCids.end(),
|
||||
[this](const std::string& cid){ return !(settings_ && settings_->isChatMuted(cid)); }))
|
||||
ui::Notifications::instance().info(TR("chat_new_message_toast"));
|
||||
}
|
||||
NetworkRefreshService::applyTransactionRefreshResult(
|
||||
@@ -2639,7 +2645,7 @@ void App::exportPrivateKey(const std::string& address, std::function<void(const
|
||||
std::string err;
|
||||
try {
|
||||
rpc::RPCClient::TraceScope trace("Settings / Export private key");
|
||||
key = rpc_->callSecret(method, {address}).get<std::string>(); // zero the raw body too (B7)
|
||||
key = rpc_->callSecretString(method, {address}); // scrubs raw body + json node (B7)
|
||||
} catch (const std::exception& e) {
|
||||
err = e.what();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user