fix(chat): wipe typed plaintext on a wallet switch + per-thread draft
Audit B1/B5/B8: - The chat composer / new-conversation buffers are file-static char[]; on a wallet switch or lock the plaintext a user typed for wallet A (a private message, or a recipient z-address) resurfaced verbatim in wallet B's composer and lingered unwiped in RAM. Add ui::ResetChatTab() (sodium_memzero the buffers + clear the selection ids) and call it from App::resetChatSession(). - Wipe the single composer draft when the active conversation changes, so text typed for one contact can't be sent to another (B5). - Refresh the stale "read-only / Phase 3" docs — composing/sending is wired (B8). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,7 @@
|
|||||||
#include "rpc/rpc_worker.h"
|
#include "rpc/rpc_worker.h"
|
||||||
#include "rpc/connection.h"
|
#include "rpc/connection.h"
|
||||||
#include "chat/chat_identity.h" // deriveChatIdentityFromSecret for HushChat identity provisioning
|
#include "chat/chat_identity.h" // deriveChatIdentityFromSecret for HushChat identity provisioning
|
||||||
|
#include "ui/windows/chat_tab.h" // ui::ResetChatTab — wipe chat UI plaintext on a wallet switch
|
||||||
#include <sodium.h> // sodium_memzero for wiping the fetched mnemonic
|
#include <sodium.h> // sodium_memzero for wiping the fetched mnemonic
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include "config/settings.h"
|
#include "config/settings.h"
|
||||||
@@ -2672,6 +2673,7 @@ void App::resetChatSession()
|
|||||||
// Drop identity + decrypted plaintext in RAM, lock the seed-encrypted DB, re-arm provisioning.
|
// Drop identity + decrypted plaintext in RAM, lock the seed-encrypted DB, re-arm provisioning.
|
||||||
chat_service_.clearIdentity();
|
chat_service_.clearIdentity();
|
||||||
chat_service_.store().clear();
|
chat_service_.store().clear();
|
||||||
|
ui::ResetChatTab(); // wipe the chat UI's typed plaintext + selection so it can't leak into the next wallet
|
||||||
chat_db_.lock();
|
chat_db_.lock();
|
||||||
chat_identity_provisioned_ = false;
|
chat_identity_provisioned_ = false;
|
||||||
chat_identity_fetch_in_flight_ = false;
|
chat_identity_fetch_in_flight_ = false;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Copyright 2024-2026 The Hush Developers
|
// Copyright 2024-2026 The Hush Developers
|
||||||
// Released under the GPLv3
|
// Released under the GPLv3
|
||||||
//
|
//
|
||||||
// chat_tab.cpp — read-only HushChat view (Phase 3): a conversation list + the
|
// chat_tab.cpp — HushChat view: a conversation list + the selected thread (both read
|
||||||
// selected thread, both read from the App-owned ChatService store.
|
// from the App-owned ChatService store), with a composer and a new-conversation flow.
|
||||||
|
|
||||||
#include "chat_tab.h"
|
#include "chat_tab.h"
|
||||||
#include "../../app.h"
|
#include "../../app.h"
|
||||||
@@ -15,6 +15,8 @@
|
|||||||
#include "../layout.h" // Layout::dpiScale()
|
#include "../layout.h" // Layout::dpiScale()
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
|
|
||||||
|
#include <sodium.h> // sodium_memzero — wipe typed plaintext on a wallet switch
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -32,6 +34,7 @@ std::string s_scroll_to_cid; // when set, scroll the thread to the bottom next
|
|||||||
|
|
||||||
// Composer + new-conversation UI state.
|
// Composer + new-conversation UI state.
|
||||||
char s_compose[512] = "";
|
char s_compose[512] = "";
|
||||||
|
std::string s_compose_cid; // the conversation s_compose is a draft for; draft is wiped when it changes
|
||||||
bool s_show_new_convo = false;
|
bool s_show_new_convo = false;
|
||||||
char s_new_zaddr[128] = "";
|
char s_new_zaddr[128] = "";
|
||||||
char s_new_msg[256] = "";
|
char s_new_msg[256] = "";
|
||||||
@@ -132,6 +135,12 @@ void RenderChatTab(App* app)
|
|||||||
s_selected_cid = convs.front().cid;
|
s_selected_cid = convs.front().cid;
|
||||||
s_scroll_to_cid = s_selected_cid;
|
s_scroll_to_cid = s_selected_cid;
|
||||||
}
|
}
|
||||||
|
// The composer holds a single draft — wipe it when the active conversation changes so text typed
|
||||||
|
// for one contact can't be sent to another (B5).
|
||||||
|
if (s_selected_cid != s_compose_cid) {
|
||||||
|
sodium_memzero(s_compose, sizeof(s_compose));
|
||||||
|
s_compose_cid = s_selected_cid;
|
||||||
|
}
|
||||||
|
|
||||||
const ImVec2 avail = ImGui::GetContentRegionAvail();
|
const ImVec2 avail = ImGui::GetContentRegionAvail();
|
||||||
const float listW = std::clamp(avail.x * 0.32f, 220.0f, 360.0f);
|
const float listW = std::clamp(avail.x * 0.32f, 220.0f, 360.0f);
|
||||||
@@ -318,5 +327,18 @@ void RenderChatTab(App* app)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResetChatTab()
|
||||||
|
{
|
||||||
|
// Securely wipe typed plaintext (a private message / recipient z-addr) so it can't resurface under
|
||||||
|
// the next wallet after a switch/lock, and drop the selection ids.
|
||||||
|
sodium_memzero(s_compose, sizeof(s_compose));
|
||||||
|
sodium_memzero(s_new_zaddr, sizeof(s_new_zaddr));
|
||||||
|
sodium_memzero(s_new_msg, sizeof(s_new_msg));
|
||||||
|
s_selected_cid.clear();
|
||||||
|
s_scroll_to_cid.clear();
|
||||||
|
s_compose_cid.clear();
|
||||||
|
s_show_new_convo = false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
@@ -11,17 +11,26 @@ class App;
|
|||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Render the Chat tab (read-only HushChat conversation view — Phase 3).
|
* @brief Render the Chat tab (HushChat conversation view).
|
||||||
*
|
*
|
||||||
* A two-pane view: a conversation list on the left and the selected thread on
|
* A two-pane view: a conversation list on the left and the selected thread on the
|
||||||
* the right, both read from the App-owned ChatService store (App::chatService()).
|
* right, both read from the App-owned ChatService store (App::chatService()). Peer
|
||||||
* Peer z-addresses are resolved to contact names via the address book when known.
|
* z-addresses are resolved to contact names via the address book when known.
|
||||||
* Read-only for now — composing/sending arrives in a later phase. The tab is only
|
* Composing/sending and starting new conversations are wired (App::sendChatMessage /
|
||||||
* reachable when built with DRAGONX_ENABLE_CHAT (gated via WalletUiSurface::Chat).
|
* startChatConversation). Reachable when built with DRAGONX_ENABLE_CHAT
|
||||||
|
* (gated via WalletUiSurface::Chat).
|
||||||
*
|
*
|
||||||
* @param app Pointer to the app instance.
|
* @param app Pointer to the app instance.
|
||||||
*/
|
*/
|
||||||
void RenderChatTab(App* app);
|
void RenderChatTab(App* app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Securely wipe the Chat tab's UI-local state (composer / new-conversation
|
||||||
|
* plaintext buffers + the selected-conversation ids). Called by
|
||||||
|
* App::resetChatSession() on a wallet switch/lock so one wallet's typed
|
||||||
|
* plaintext can't resurface (or linger unwiped in RAM) under the next wallet.
|
||||||
|
*/
|
||||||
|
void ResetChatTab();
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace dragonx
|
} // namespace dragonx
|
||||||
|
|||||||
Reference in New Issue
Block a user