security: wipe RPC creds, lock down generated conf, auto-clear secret clipboard (audit #4-6)
- rpc_client: wipe the plaintext "user:password" temporary with sodium_memzero after base64-encoding it into the auth header (std::string doesn't zero its buffer on destruction). - connection: the auto-generated DRAGONX.conf holds rpcuser/rpcpassword in plaintext but was written with the default umask (often world-readable 0644). Restrict it to owner read/write after creation so another local user can't read the credentials. - app: copying a seed phrase / private key to the clipboard now arms an auto-clear — App::copySecretToClipboard() copies the secret and, after 45s, wipes the clipboard IF it still holds that secret (compared via a stored hash, never the plaintext). Wired into the lite first-run wizard's seed Copy and the Settings export-secret Copy, with a "clipboard auto-clears in 45s" notice. pumpSecretClipboardClear() runs each frame. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
32
src/app.cpp
32
src/app.cpp
@@ -566,6 +566,9 @@ void App::update()
|
||||
using RefreshTimer = services::NetworkRefreshService::Timer;
|
||||
network_refresh_.tick(io.DeltaTime);
|
||||
|
||||
// Wipe a secret (seed/private key) from the clipboard once its auto-clear delay elapses.
|
||||
pumpSecretClipboardClear();
|
||||
|
||||
// Full-node RPC refreshes gate on ACTUAL RPC connectivity, not state_.connected. In lite
|
||||
// builds state_.connected is the lite-wallet "online" proxy (true when a wallet is open, to
|
||||
// enable the wallet UI), but there is no RPC daemon — so RPC polls (mining/balance/peers/txs)
|
||||
@@ -2072,7 +2075,7 @@ void App::renderLiteFirstRunPrompt()
|
||||
step = 2;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Copy", ImVec2(80, 0))) ImGui::SetClipboardText(seed.c_str());
|
||||
if (ImGui::Button("Copy", ImVec2(80, 0))) copySecretToClipboard(seed);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Skip", ImVec2(80, 0))) {
|
||||
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
|
||||
@@ -3659,6 +3662,33 @@ std::string App::transactionRefreshProgressText() const
|
||||
return TR("tx_loading_fetching_transparent");
|
||||
}
|
||||
|
||||
void App::copySecretToClipboard(const std::string& secret)
|
||||
{
|
||||
ImGui::SetClipboardText(secret.c_str());
|
||||
// Keep only an FNV-1a hash of the secret so we can later confirm the clipboard still holds it
|
||||
// (and shouldn't be clobbered) WITHOUT retaining the plaintext.
|
||||
std::uint64_t h = 1469598103934665603ULL;
|
||||
for (unsigned char c : secret) h = (h ^ c) * 1099511628211ULL;
|
||||
clipboard_secret_hash_ = secret.empty() ? 0 : h;
|
||||
clipboard_clear_deadline_ = secret.empty() ? 0.0 : (ImGui::GetTime() + 45.0);
|
||||
if (!secret.empty())
|
||||
ui::Notifications::instance().info("Copied — clipboard auto-clears in 45s", 4.0f);
|
||||
}
|
||||
|
||||
void App::pumpSecretClipboardClear()
|
||||
{
|
||||
if (clipboard_clear_deadline_ <= 0.0) return;
|
||||
if (ImGui::GetTime() < clipboard_clear_deadline_) return;
|
||||
// Only clear if the clipboard STILL holds our secret (the user may have copied something else).
|
||||
if (const char* cb = ImGui::GetClipboardText()) {
|
||||
std::uint64_t h = 1469598103934665603ULL;
|
||||
for (const char* p = cb; *p; ++p) h = (h ^ static_cast<unsigned char>(*p)) * 1099511628211ULL;
|
||||
if (h == clipboard_secret_hash_) ImGui::SetClipboardText("");
|
||||
}
|
||||
clipboard_clear_deadline_ = 0.0;
|
||||
clipboard_secret_hash_ = 0;
|
||||
}
|
||||
|
||||
void App::maybeFinishTransactionSendProgress()
|
||||
{
|
||||
using Job = services::NetworkRefreshService::Job;
|
||||
|
||||
Reference in New Issue
Block a user