From 5d9bd1688cf05ce53a19a4b3cd0e89d54dc539aa Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 18:16:50 -0500 Subject: [PATCH] fix(security): clear the secret clipboard on a monotonic clock A secret copied to the clipboard auto-clears after a timeout, but the deadline was stored as an ImGui::GetTime() value. That clock only advances while frames are being drawn, so while the app idled (no redraws) it effectively stood still and the pending clear could be put off well past the intended window. - The clear deadline is now a steady_clock time_point, which keeps counting whether or not the UI is rendering. - The pump uses the armed hash as its "something is pending" flag rather than a sentinel deadline value, and compares the real wall time. Co-Authored-By: Claude Opus 4.8 --- src/app.cpp | 10 ++++++---- src/app.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/app.cpp b/src/app.cpp index 45a8658..da60c88 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7961,7 +7961,9 @@ void App::copySecretToClipboard(const std::string& secret) 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); + clipboard_clear_deadline_ = secret.empty() + ? std::chrono::steady_clock::time_point{} + : (std::chrono::steady_clock::now() + std::chrono::seconds(45)); if (!secret.empty()) ui::Notifications::instance().info(TR("appx_copied_clipboard_autoclears"), 4.0f); } @@ -7975,14 +7977,14 @@ void App::clearSecretClipboardIfArmed() for (const char* p = cb; *p; ++p) h = (h ^ static_cast(*p)) * 1099511628211ULL; if (h == clipboard_secret_hash_) ImGui::SetClipboardText(""); } - clipboard_clear_deadline_ = 0.0; + clipboard_clear_deadline_ = {}; clipboard_secret_hash_ = 0; } void App::pumpSecretClipboardClear() { - if (clipboard_clear_deadline_ <= 0.0) return; - if (ImGui::GetTime() < clipboard_clear_deadline_) return; + if (clipboard_secret_hash_ == 0) return; // armed flag + if (std::chrono::steady_clock::now() < clipboard_clear_deadline_) return; clearSecretClipboardIfArmed(); } diff --git a/src/app.h b/src/app.h index 0d4a092..ded25d0 100644 --- a/src/app.h +++ b/src/app.h @@ -1601,7 +1601,7 @@ private: void armScanGate(const services::NetworkRefreshService::ConnectionInfoResult& info); // Auto-clear for secrets copied to the clipboard. Only a hash of the copied secret is kept. std::uint64_t clipboard_secret_hash_ = 0; - double clipboard_clear_deadline_ = 0.0; + std::chrono::steady_clock::time_point clipboard_clear_deadline_{}; float loading_timer_ = 0.0f; // spinner animation for loading overlay double connect_stall_since_ = 0.0; // ImGui::GetTime() when the daemon first went "reachable but not ready"; 0 = not stalling (see util/connect_stall.h) bool encryption_incomplete_warned_ = false; // W2-2: once-per-session guard for the "encryption didn't complete" warning