Files
ObsidianDragon/src/ui/windows/console_tab_helpers.cpp
T
DanSandClaude Opus 4.8 83ce73f26b security(console): mask key-dump command RESULTS in the scrollback
The console redacted secret command ARGS but showed the OUTPUT of key-revealing
commands (dumpprivkey / z_exportkey / z_exportviewingkey / z_exportmnemonic /
dumpwallet / z_exportwallet, and the lite seed/export verbs) in full — visible
in the scrollback, Copy All, Ctrl+C and the filter. Now, when the submitted
command is key-revealing, its result is held ONLY in a member buffer (never in
the scrollback model, so Copy-All etc. can't leak it) and a masked placeholder
is shown. An explicit "Copy secret" button routes through the auto-clearing
clipboard; the buffer auto-wipes after 60s, on Clear, and on wallet lock
(via wipeDisplayedSecrets). The command string passed to the executor is
byte-identical. New pure helper ConsoleCommandRevealsSecret is unit-testable.

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

108 lines
4.1 KiB
C++

#include "console_tab_helpers.h"
#include <algorithm>
#include <cctype>
namespace dragonx {
namespace ui {
namespace {
// First tokens (lowercase) of console/RPC commands that carry a secret argument on the command line.
// Output-secret commands (dumpprivkey / z_exportkey / z_exportmnemonic) are deliberately absent —
// their secret is in the RESULT, which is a separate redaction concern.
const char* const kSecretConsoleCommands[] = {
"walletpassphrase", "walletpassphrasechange", "encryptwallet",
"importprivkey", "importwallet", "importmulti",
"z_importkey", "z_importviewingkey", "z_importwallet",
"signrawtransaction", "magicrecoverkey", "sethdseed", "importmnemonic",
// Lite-wallet verbs (console_command_reference kLiteKeyCommands): these take a spending/viewing key
// or a password as their argument. Full-node has no bare import/encrypt/decrypt/unlock verb, so a
// flat match is safe.
"import", "timport", "encrypt", "decrypt", "unlock",
};
std::string firstConsoleTokenLower(const std::string& cmd, size_t& tokenEnd) {
size_t b = cmd.find_first_not_of(" \t");
if (b == std::string::npos) { tokenEnd = cmd.size(); return {}; }
size_t e = cmd.find_first_of(" \t", b);
tokenEnd = (e == std::string::npos) ? cmd.size() : e;
std::string t = cmd.substr(b, tokenEnd - b);
std::transform(t.begin(), t.end(), t.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return t;
}
} // namespace
float ComputeConsoleInputHeight(float frameHeightWithSpacing,
float itemSpacingY,
float spacingSm,
float spacingXs,
float cursorOffset)
{
return spacingSm + itemSpacingY + frameHeightWithSpacing + spacingSm + spacingXs + cursorOffset;
}
float ComputeConsoleOutputHeight(float availableHeight,
float inputHeight,
float minHeight,
float minHeightRatio)
{
return std::max(availableHeight - inputHeight, std::max(minHeight, availableHeight * minHeightRatio));
}
float ClampConsoleWrapWidth(float contentWidth, float paddingX)
{
return std::max(50.0f, contentWidth - paddingX * 2.0f);
}
bool ShouldPrintDaemonErrorLine(bool wasError, bool isError,
const std::string& error, const std::string& lastPrinted,
bool newUnexpectedExit)
{
return isError && (!wasError || error != lastPrinted || newUnexpectedExit);
}
bool ConsoleCommandCarriesSecret(const std::string& cmd)
{
size_t end = 0;
const std::string name = firstConsoleTokenLower(cmd, end);
if (name.empty()) return false;
for (const char* s : kSecretConsoleCommands) if (name == s) return true;
return false;
}
bool ConsoleCommandRevealsSecret(const std::string& cmd, bool fullNode)
{
size_t end = 0;
const std::string name = firstConsoleTokenLower(cmd, end);
if (name.empty()) return false;
if (fullNode) {
static const char* const kFull[] = {
"dumpprivkey", "z_exportkey", "z_exportviewingkey",
"z_exportmnemonic", "dumpwallet", "z_exportwallet",
};
for (const char* s : kFull) if (name == s) return true;
} else {
// Lite backend verbs whose output is the seed phrase / private keys.
static const char* const kLite[] = { "seed", "export" };
for (const char* s : kLite) if (name == s) return true;
}
return false;
}
std::string RedactConsoleCommand(const std::string& cmd)
{
size_t end = 0;
const std::string name = firstConsoleTokenLower(cmd, end);
if (name.empty()) return cmd;
bool secret = false;
for (const char* s : kSecretConsoleCommands) if (name == s) { secret = true; break; }
if (!secret) return cmd;
// Only redact if there are actually arguments after the command name.
if (cmd.find_first_not_of(" \t", end) == std::string::npos) return cmd;
return cmd.substr(0, end) + " ****";
}
} // namespace ui
} // namespace dragonx