Security: commandAllowList bypass via command substitution in double quotes in helpers::tokenizeCommand
Describe the bug
A security flaw in helpers::tokenizeCommand (helpers.cpp) allows arbitrary command execution that bypasses commandAllowList when double quotes are used.
In helpers::tokenizeCommand, outside quotes, shell metacharacters (including $ and `) reject the command via:
else if(strchr(";|&><$`(){}\\\n\r", c) != nullptr) {
tokens.clear();
return tokens;
}However, inside double quotes (inDouble), the characters $ and ` are not rejected and are appended directly to current:
else { // inDouble
if(c == '"') {
inDouble = false;
}
else if(c == '\\' && i + 1 < command.size()) {
current += command[++i];
}
else {
current += static_cast<char>(c);
}
}When commands are executed on Unix/macOS platforms, TinyProcessLib::Process executes commands using /bin/sh -c "<command>". In POSIX shells, double quotes do NOT prevent command substitution:
$(...)is evaluated and executed by/bin/sh`...`is evaluated and executed by/bin/sh
Consequently, if commandAllowList permits any binary (e.g. node, git, python, echo), any double-quoted argument containing $(...) or `...` causes tokens[0] to be parsed as the allowed program name, passing permission::hasCommandExecutionAccess, but /bin/sh evaluates and executes the nested command substitution.
To Reproduce
- Configure
commandAllowListinneutralino.config.json:{ "commandAllowList": ["node"] } - Attempt to execute an allowed command with nested command substitution inside double quotes:or with backticks:
await Neutralino.os.execCommand('node -e "console.log(1)" "$(whoami)"');await Neutralino.os.execCommand('node -e "console.log(1)" "`whoami`"'); helpers::tokenizeCommandparses the command tokens:tokens[0] = "node"tokens[1] = "-e"tokens[2] = "console.log(1)"tokens[3] = "$(whoami)"
permission::hasCommandExecutionAccesscheckstokens[0] == "node", which matches the allow-list and returnstrue.- Neutralino invokes
/bin/sh -cwith the unquoted command string, which evaluates$(whoami)and executes arbitrary shell commands.
Expected behavior
helpers::tokenizeCommand should reject commands containing shell substitution operators ($ and `) inside double quotes, preventing /bin/sh from executing subshells or variable expansions within allowed commands.
Specifications
- OS: Linux / macOS (POSIX shells)
- Affects:
helpers.cpp(helpers::tokenizeCommand)
Proposed Fix
In helpers.cpp, reject the command if $ or ` is encountered inside double quotes:
diff --git a/helpers.cpp b/helpers.cpp
index 9ec1c03..146a8bc 100644
--- a/helpers.cpp
+++ b/helpers.cpp
@@ -280,6 +280,10 @@ vector<string> tokenizeCommand(const string &command) {
else if(c == '\\' && i + 1 < command.size()) {
current += command[++i];
}
+ else if(c == '$' || c == '`') {
+ tokens.clear();
+ return tokens;
+ }
else {
current += static_cast<char>(c);
}Source: neutralinojs/neutralinojs