fix(console): redact secret-bearing commands from the console echo and history (W7-1)

The RPC console echoed and stored typed commands verbatim, so `walletpassphrase
<secret>`, `z_importkey <key>`, `encryptwallet <pass>` etc. left the secret in the
visible log AND the 100-entry recall history (copyable). Adds a pure, unit-testable
RedactConsoleCommand()/ConsoleCommandCarriesSecret() (allowlist of 13 secret-bearing
first-tokens) in console_tab_helpers; submitConsoleCommand() now echoes and stores
`> walletpassphrase ****` while still executing the real command unredacted. Bare
secret commands and non-secret commands pass through unchanged.

Output-secret commands (dumpprivkey / z_exportkey / z_exportmnemonic) — whose secret is
in the RESULT — are a separate redaction concern, tracked as a follow-up.

First fix in the wallet-hardening P0-A cluster (see docs/wallet-hardening.md). New
testConsoleSecretRedaction (11 assertions); ctest 1/1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-08-02 13:57:52 -05:00
parent d188a08db7
commit da0e9f5915
5 changed files with 201 additions and 2 deletions

View File

@@ -2523,6 +2523,29 @@ void testAllowsPlaintextRemote()
EXPECT_TRUE(!Connection::usesPlaintextRemote(remoteTls)); // TLS → not plaintext, never refused
}
void testConsoleSecretRedaction()
{
using dragonx::ui::RedactConsoleCommand;
using dragonx::ui::ConsoleCommandCarriesSecret;
// Secret-bearing commands are recognized (case- and whitespace-insensitive on the name).
EXPECT_TRUE(ConsoleCommandCarriesSecret("walletpassphrase myPass 60"));
EXPECT_TRUE(ConsoleCommandCarriesSecret("z_importkey SK-secret"));
EXPECT_TRUE(ConsoleCommandCarriesSecret(" ENCRYPTWALLET topsecret"));
EXPECT_TRUE(!ConsoleCommandCarriesSecret("getinfo"));
EXPECT_TRUE(!ConsoleCommandCarriesSecret("getwalletinfo")); // not a false-positive substring match
// Redaction replaces the arguments with **** but preserves the (original-case) command name.
EXPECT_EQ(RedactConsoleCommand("walletpassphrase myPass 60"), std::string("walletpassphrase ****"));
EXPECT_EQ(RedactConsoleCommand("z_importkey SK-secret-key"), std::string("z_importkey ****"));
EXPECT_EQ(RedactConsoleCommand("ENCRYPTWALLET topsecret"), std::string("ENCRYPTWALLET ****"));
// A bare secret command with no argument is left unchanged (nothing to hide).
EXPECT_EQ(RedactConsoleCommand("walletpassphrase"), std::string("walletpassphrase"));
// Non-secret commands pass through untouched.
EXPECT_EQ(RedactConsoleCommand("sendtoaddress addr 1.0"), std::string("sendtoaddress addr 1.0"));
EXPECT_EQ(RedactConsoleCommand("getwalletinfo"), std::string("getwalletinfo"));
}
void testConnectHasStalled()
{
using dragonx::util::connectHasStalled;
@@ -6861,6 +6884,7 @@ int main()
testConnectHasStalled();
testIsLocalHost();
testAllowsPlaintextRemote();
testConsoleSecretRedaction();
testDaemonLifecycleExecution();
testDaemonLifecycleAdapters();
testConsoleTextLayout();