Files
ObsidianDragon/src/ui/windows/seed_display.h
T
DanSandClaude Opus 4.8 c0d10e9664 security(hygiene): best-effort scrubbing of residual in-memory secret copies (Tier 4)
Defense-in-depth against a local attacker / core dump reading un-zeroed heap
copies of secrets (threat model is local, not remote). Best-effort by nature —
std::string SSO/reallocation and ImGui buffer growth can still strand copies —
so these shrink the exposure window rather than guarantee absence:
- rpc_client performCall: a scope guard now sodium_memzeroes the request body +
  payload tree (walletpassphrase / *_importkey args) after the call and detaches
  CURLOPT_POSTFIELDS. Request content sent to curl is unchanged.
- New ui::scrubImGuiInputTextState() zeroes ImGui's retained InputText buffers
  (live/revert/callback/deactivated) — the app memzeroed its own char[]s but
  ImGui kept copies. Called from wipeSecrets()/wipeDisplayedSecrets(). PINNED to
  ImGui 1.92.6 via a #error guard so a vendored bump forces a re-audit (the stb
  undo buffer is an incomplete type in the header, so it's left as a noted gap).
- Lite exportSeed/exportPrivateKeys wipe result.value + the parsed JSON tree.
- Seed-word grid (SplitSeedWords/RenderSeedWordGrid) zeroes its per-frame word
  and cell copies; the wizard word/chip vectors are zeroed before clearing.
- ChatStore::clear() + ResetChatTab() zero decrypted message bodies on
  lock/switch/teardown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 12:05:04 -05:00

75 lines
2.8 KiB
C++

#pragma once
// Shared helpers for rendering a BIP39 seed phrase as a numbered word grid and for
// splitting a whitespace-separated phrase into words. Used by the full-node seed-phrase
// backup dialog and the "migrate to a seed wallet" flow so both present the phrase the
// same way the lite welcome wizard does.
#include <cstdio>
#include <string>
#include <vector>
#include <sodium.h>
#include "imgui.h"
#include "../layout.h" // Layout::dpiScale()
namespace dragonx {
namespace ui {
// Best-effort zero of a seed-word vector (each word holds part of the phrase). Call after rendering.
inline void WipeSeedWords(std::vector<std::string>& words)
{
for (auto& w : words) { if (!w.empty()) sodium_memzero(&w[0], w.size()); w.clear(); }
words.clear();
}
// Split a whitespace-separated seed phrase into its individual words.
inline std::vector<std::string> SplitSeedWords(const std::string& phrase)
{
std::vector<std::string> words;
words.reserve(32); // avoid reallocation stranding un-wiped moved-from copies in freed heap
std::string cur;
for (char c : phrase) {
if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
if (!cur.empty()) {
words.push_back(cur);
sodium_memzero(&cur[0], cur.size()); // zero the scratch copy before reuse
cur.clear();
}
} else {
cur.push_back(c);
}
}
if (!cur.empty()) { words.push_back(cur); sodium_memzero(&cur[0], cur.size()); cur.clear(); }
return words;
}
// Render the words as a numbered 4-column grid (matches the lite welcome wizard layout).
// The column stride is sized to the widest "NN. word" cell and scaled by dpiScale(): a raw px
// stride collided with the next column's index once the font grew at higher DPI / font scale
// (e.g. at 150% "13. elevator" ran straight into "14."). Sizing to content keeps a gap at any
// scale and for any word length.
inline void RenderSeedWordGrid(const std::vector<std::string>& words, float colSpacingPx = 130.0f)
{
const float dp = Layout::dpiScale();
float colStride = colSpacingPx * dp;
for (size_t i = 0; i < words.size(); ++i) {
char cell[96];
std::snprintf(cell, sizeof(cell), "%2zu. %s", i + 1, words[i].c_str());
const float need = ImGui::CalcTextSize(cell).x + 20.0f * dp; // cell width + inter-column gap
if (need > colStride) colStride = need;
sodium_memzero(cell, sizeof(cell)); // the cell held a seed word
}
for (size_t i = 0; i < words.size(); ++i) {
char cell[96];
std::snprintf(cell, sizeof(cell), "%2zu. %s", i + 1, words[i].c_str());
ImGui::TextUnformatted(cell);
if ((i % 4) != 3 && i + 1 < words.size())
ImGui::SameLine(((i % 4) + 1) * colStride);
sodium_memzero(cell, sizeof(cell));
}
}
} // namespace ui
} // namespace dragonx