Skip to content

Commit 5624f73

Browse files
committed
qt: prevent re-execution of sensitive commands from console history
1 parent 5c5704e commit 5624f73

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

src/qt/rpcconsole.cpp

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ const QStringList historyFilter = QStringList()
8383

8484
}
8585

86+
// List of commands that may cause unintended effects if accidentally re-executed from history
87+
const QStringList sensitiveFilter = QStringList()
88+
<< "send"
89+
<< "sendall"
90+
<< "sendmany"
91+
<< "sendtoaddress"
92+
;
93+
8694
/* Object for executing console RPC commands in a separate thread.
8795
*/
8896
class RPCExecutor : public QObject
@@ -361,6 +369,23 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes
361369
for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) {
362370
pstrFilteredOut->replace(i->first, i->second - i->first, "(…)");
363371
}
372+
373+
bool is_sensitive = !filter_ranges.empty();
374+
375+
if (!is_sensitive) {
376+
const QString strcmd = QString::fromStdString(*pstrFilteredOut);
377+
for (const QString& val : sensitiveFilter) {
378+
if (strcmd.contains(val, Qt::CaseInsensitive)) {
379+
is_sensitive = true;
380+
break;
381+
}
382+
}
383+
}
384+
385+
// Prefix "!" to mark sensitive commands as non-executable when recalled from history
386+
if (is_sensitive) {
387+
pstrFilteredOut->insert(0, 1, '!');
388+
}
364389
}
365390
switch(state) // final state
366391
{
@@ -405,7 +430,11 @@ void RPCExecutor::request(const QString &command, const QString& wallet_name)
405430
" example: getblock(getblockhash(0) 1)[tx]\n\n"
406431

407432
"Results without keys can be queried with an integer in brackets using the parenthesized syntax.\n"
408-
" example: getblock(getblockhash(0),1)[tx][0]\n\n")));
433+
" example: getblock(getblockhash(0),1)[tx][0]\n\n"
434+
435+
"Commands starting with a leading '!' are blocked from execution.\n"
436+
"These entries are shown for reference only. Remove the '!' or retype to run them.\n"
437+
" example: !walletpassphrase(...)\n\n")));
409438
return;
410439
}
411440
if (!RPCConsole::RPCExecuteCommandLine(m_node, result, executableCommand, nullptr, wallet_name)) {
@@ -994,6 +1023,16 @@ void RPCConsole::on_lineEdit_returnPressed()
9941023
return;
9951024
}
9961025

1026+
// Prevent parsing and execution of commands prefixed with '!'
1027+
if (cmd.startsWith('!')) {
1028+
QMessageBox::information(this, tr("Command not executed"), tr(
1029+
"Commands prefixed with '!' are blocked.\n"
1030+
"Remove the '!' or retype to run again."
1031+
1032+
));
1033+
return;
1034+
}
1035+
9971036
std::string strFilteredCmd;
9981037
try {
9991038
std::string dummy;

src/qt/test/rpcnestedtests.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,34 @@ void RPCNestedTests::rpcNestedTests()
8585
QVERIFY(result == "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b");
8686
QVERIFY(filtered == "getblock(getbestblockhash())[tx][0]");
8787

88+
RPCConsole::RPCParseCommandLine(nullptr, result, "createwallet test true", false, &filtered);
89+
QVERIFY(filtered == "!createwallet(…)");
90+
RPCConsole::RPCParseCommandLine(nullptr, result, "createwalletdescriptor abc", false, &filtered);
91+
QVERIFY(filtered == "!createwalletdescriptor(…)");
92+
RPCConsole::RPCParseCommandLine(nullptr, result, "migratewallet abc abc", false, &filtered);
93+
QVERIFY(filtered == "!migratewallet(…)");
8894
RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc", false, &filtered);
89-
QVERIFY(filtered == "signmessagewithprivkey(…)");
95+
QVERIFY(filtered == "!signmessagewithprivkey(…)");
9096
RPCConsole::RPCParseCommandLine(nullptr, result, "signmessagewithprivkey abc,def", false, &filtered);
91-
QVERIFY(filtered == "signmessagewithprivkey(…)");
97+
QVERIFY(filtered == "!signmessagewithprivkey(…)");
9298
RPCConsole::RPCParseCommandLine(nullptr, result, "signrawtransactionwithkey(abc)", false, &filtered);
93-
QVERIFY(filtered == "signrawtransactionwithkey(…)");
99+
QVERIFY(filtered == "!signrawtransactionwithkey(…)");
94100
RPCConsole::RPCParseCommandLine(nullptr, result, "walletpassphrase(help())", false, &filtered);
95-
QVERIFY(filtered == "walletpassphrase(…)");
101+
QVERIFY(filtered == "!walletpassphrase(…)");
96102
RPCConsole::RPCParseCommandLine(nullptr, result, "walletpassphrasechange(help(walletpassphrasechange(abc)))", false, &filtered);
97-
QVERIFY(filtered == "walletpassphrasechange(…)");
103+
QVERIFY(filtered == "!walletpassphrasechange(…)");
98104
RPCConsole::RPCParseCommandLine(nullptr, result, "help(encryptwallet(abc, def))", false, &filtered);
99-
QVERIFY(filtered == "help(encryptwallet(…))");
105+
QVERIFY(filtered == "!help(encryptwallet(…))");
106+
107+
// Test filtering for sensitive commands
108+
RPCConsole::RPCParseCommandLine(nullptr, result, "send abc abc", false, &filtered);
109+
QVERIFY(filtered == "!send abc abc");
110+
RPCConsole::RPCParseCommandLine(nullptr, result, "sendall abc abc", false, &filtered);
111+
QVERIFY(filtered == "!sendall abc abc");
112+
RPCConsole::RPCParseCommandLine(nullptr, result, "sendmany abc abc", false, &filtered);
113+
QVERIFY(filtered == "!sendmany abc abc");
114+
RPCConsole::RPCParseCommandLine(nullptr, result, "sendtoaddress abc abc", false, &filtered);
115+
QVERIFY(filtered == "!sendtoaddress abc abc");
100116

101117
RPCConsole::RPCExecuteCommandLine(m_node, result, "rpcNestedTest");
102118
QVERIFY(result == "[]");

0 commit comments

Comments
 (0)