diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index d6d2be7b393..ae8f44091d9 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -83,6 +83,14 @@ const QStringList historyFilter = QStringList() } +// List of commands that may cause unintended effects if accidentally re-executed from history +const QStringList sensitiveFilter = QStringList() + << "send" + << "sendall" + << "sendmany" + << "sendtoaddress" +; + /* Object for executing console RPC commands in a separate thread. */ class RPCExecutor : public QObject @@ -361,6 +369,23 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) { pstrFilteredOut->replace(i->first, i->second - i->first, "(…)"); } + + bool is_sensitive = !filter_ranges.empty(); + + if (!is_sensitive) { + const QString strcmd = QString::fromStdString(*pstrFilteredOut); + for (const QString& val : sensitiveFilter) { + if (strcmd.contains(val, Qt::CaseInsensitive)) { + is_sensitive = true; + break; + } + } + } + + // Prefix "!" to mark sensitive commands as non-executable when recalled from history + if (is_sensitive) { + pstrFilteredOut->insert(0, 1, '!'); + } } switch(state) // final state { @@ -405,7 +430,11 @@ void RPCExecutor::request(const QString &command, const QString& wallet_name) " example: getblock(getblockhash(0) 1)[tx]\n\n" "Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n" - " example: getblock(getblockhash(0),1)[tx][0]\n\n"))); + " example: getblock(getblockhash(0),1)[tx][0]\n\n" + + "Commands starting with a leading '!' are blocked from execution.\n" + "These entries are shown for reference only. Remove the '!' or retype to run them.\n" + " example: !walletpassphrase(...)\n\n"))); return; } if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_name)) { @@ -994,6 +1023,16 @@ void RPCConsole::on_lineEdit_returnPressed() return; } + // Prevent parsing and execution of commands prefixed with '!' + if (cmd.startsWith('!')) { + QMessageBox::information(this, tr("Command not executed"), tr( + "Commands prefixed with '!' are blocked.\n" + "Remove the '!' or retype to run again." + + )); + return; + } + std::string strFilteredCmd; try { std::string dummy; diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 0857a4ebd5a..486a434f225 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -85,18 +85,34 @@ void RPCNestedTests::rpcNestedTests() QVERIFY(result == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"); QVERIFY(filtered == "getblock(getbestblockhash())[tx][0]"); + RPCConsole::RPCParseCommandLine(nullptr, result, "createwallet test true", false, &filtered); + QVERIFY(filtered == "!createwallet(…)"); + RPCConsole::RPCParseCommandLine(nullptr, result, "createwalletdescriptor abc", false, &filtered); + QVERIFY(filtered == "!createwalletdescriptor(…)"); + RPCConsole::RPCParseCommandLine(nullptr, result, "migratewallet abc abc", false, &filtered); + QVERIFY(filtered == "!migratewallet(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc", false, &filtered); - QVERIFY(filtered == "signmessagewithprivkey(…)"); + QVERIFY(filtered == "!signmessagewithprivkey(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc,def", false, &filtered); - QVERIFY(filtered == "signmessagewithprivkey(…)"); + QVERIFY(filtered == "!signmessagewithprivkey(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "signrawtransactionwithkey(abc)", false, &filtered); - QVERIFY(filtered == "signrawtransactionwithkey(…)"); + QVERIFY(filtered == "!signrawtransactionwithkey(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "walletpassphrase(help())", false, &filtered); - QVERIFY(filtered == "walletpassphrase(…)"); + QVERIFY(filtered == "!walletpassphrase(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "walletpassphrasechange(help(walletpassphrasechange(abc)))", false, &filtered); - QVERIFY(filtered == "walletpassphrasechange(…)"); + QVERIFY(filtered == "!walletpassphrasechange(…)"); RPCConsole::RPCParseCommandLine(nullptr, result, "help(encryptwallet(abc, def))", false, &filtered); - QVERIFY(filtered == "help(encryptwallet(…))"); + QVERIFY(filtered == "!help(encryptwallet(…))"); + + // Test filtering for sensitive commands + RPCConsole::RPCParseCommandLine(nullptr, result, "send abc abc", false, &filtered); + QVERIFY(filtered == "!send abc abc"); + RPCConsole::RPCParseCommandLine(nullptr, result, "sendall abc abc", false, &filtered); + QVERIFY(filtered == "!sendall abc abc"); + RPCConsole::RPCParseCommandLine(nullptr, result, "sendmany abc abc", false, &filtered); + QVERIFY(filtered == "!sendmany abc abc"); + RPCConsole::RPCParseCommandLine(nullptr, result, "sendtoaddress abc abc", false, &filtered); + QVERIFY(filtered == "!sendtoaddress abc abc"); RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest"); QVERIFY(result == "[]");