fix(ui): validate and clamp dialog input

Tighten input handling across several dialogs so bad/edge input can't
produce a doomed request or a confusing display:

- app_security: passphrase strength meter now factors character-class
  diversity — an all-one-class string downgrades one tier so "aaaaaaaa"
  no longer scores as high as a mixed one.
- block_info: clamp the height field to the chain tip and hide/deny
  "Next" at the tip (was only yielding a raw RPC error).
- address_label: trim surrounding whitespace before saving; a
  whitespace-only label clears it (mirrors clearing the icon).
- send_tab: re-clamp the amount when a Max send has its fee bumped in the
  confirm popup (kept the total within budget) and NUL-terminate the
  strncpy'd address/memo when a payment URI overwrites the form.
- settings_page: reject an empty/whitespace-only lite wallet path before
  dispatching an unusable open/restore request.
- peers_tab: guard ExtractIP against an empty address.
- transaction_details: disable "View in explorer" when the configured
  explorer URL is empty/whitespace so we never open a garbage link.
- app: seed-backup "Skip" now requires a second, deliberate click with a
  fund-loss warning before it dismisses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-05 15:08:59 -05:00
parent 41f5548593
commit 658c0f355b
8 changed files with 118 additions and 14 deletions

View File

@@ -2278,6 +2278,7 @@ void App::renderLiteFirstRunPrompt()
static std::vector<std::pair<std::string, bool>> chips; // shuffled (word, consumed)
static int progress = 0; // # words confirmed in order
static double wrongFlashUntil = 0.0; // brief "not the next word" hint
static bool skipConfirm = false; // "Skip" backup requires a second confirming click
static bool creating = false; // async create (with failover) in flight
// Restore-from-seed step (step 3). The seed buffer is SECRET — wiped in finish() and on Back.
static char restoreSeed[512] = {};
@@ -2305,6 +2306,7 @@ void App::renderLiteFirstRunPrompt()
chips.clear();
progress = 0;
step = 0;
skipConfirm = false;
creating = false;
lite_firstrun_dismissed_ = true;
ImGui::CloseCurrentPopup();
@@ -2415,14 +2417,28 @@ void App::renderLiteFirstRunPrompt()
std::mt19937 rng{std::random_device{}()};
std::shuffle(chips.begin(), chips.end(), rng);
progress = 0;
skipConfirm = false;
step = 2;
}
ImGui::SameLine();
if (ui::material::TactileButton("Copy", ImVec2(80, 0))) copySecretToClipboard(seed);
ImGui::SameLine();
if (ui::material::TactileButton("Skip", ImVec2(80, 0))) {
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
finish();
if (ui::material::TactileButton(skipConfirm ? "Skip anyway" : "Skip", ImVec2(120, 0))) {
if (!skipConfirm) {
skipConfirm = true; // require a second, deliberate click
} else {
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
finish();
}
}
if (skipConfirm) {
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. "
"Skip anyway?");
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
}
} else if (step == 2) {
// ── Verify: tap the words in order ──────────────────────────────────────
@@ -2471,11 +2487,24 @@ void App::renderLiteFirstRunPrompt()
}
if (!verified) ImGui::EndDisabled();
ImGui::SameLine();
if (ui::material::TactileButton("Back", ImVec2(80, 0))) step = 1;
if (ui::material::TactileButton("Back", ImVec2(80, 0))) { skipConfirm = false; step = 1; }
ImGui::SameLine();
if (ui::material::TactileButton("Skip", ImVec2(80, 0))) {
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
finish();
if (ui::material::TactileButton(skipConfirm ? "Skip anyway" : "Skip", ImVec2(120, 0))) {
if (!skipConfirm) {
skipConfirm = true; // require a second, deliberate click
} else {
ui::Notifications::instance().success(TR("lite_welcome_created"), 6.0f);
finish();
}
}
if (skipConfirm) {
ImGui::Spacing();
ImGui::PushTextWrapPos(ImGui::GetCursorPosX() + 380.0f);
ImGui::PushStyleColor(ImGuiCol_Text, ui::material::Error());
ImGui::TextUnformatted("You have not backed up your seed — funds could be lost. "
"Skip anyway?");
ImGui::PopStyleColor();
ImGui::PopTextWrapPos();
}
} else if (step == 3) {
// ── Restore from an existing seed phrase ────────────────────────────────