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 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 18:16:50 -05:00
co-authored by Claude Opus 4.8
parent f3f85d602d
commit 5d9bd1688c
2 changed files with 7 additions and 5 deletions
+6 -4
View File
@@ -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<unsigned char>(*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();
}