Security: commandAllowList bypass via command substitution in double quotes in helpers::tokenizeCommand

Author: Pcmhacker-piroCreated Sep 11, 2026Updated Sep 11, 2026

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:

cpp
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:

cpp
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

  1. Configure commandAllowList in neutralino.config.json:
    json
    {
      "commandAllowList": ["node"]
    }
  2. Attempt to execute an allowed command with nested command substitution inside double quotes:
    javascript
    await Neutralino.os.execCommand('node -e "console.log(1)" "$(whoami)"');
    or with backticks:
    javascript
    await Neutralino.os.execCommand('node -e "console.log(1)" "`whoami`"');
  3. helpers::tokenizeCommand parses the command tokens:
    • tokens[0] = "node"
    • tokens[1] = "-e"
    • tokens[2] = "console.log(1)"
    • tokens[3] = "$(whoami)"
  4. permission::hasCommandExecutionAccess checks tokens[0] == "node", which matches the allow-list and returns true.
  5. Neutralino invokes /bin/sh -c with 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
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