How DesktopCommander Extracts Commands from `$()` and Backtick Substitutions

DesktopCommander recursively parses command substitution syntax—both $() and backticks—to extract nested commands for security validation before execution.

Command extraction in shell environments presents a significant security challenge: malicious inputs often hide prohibited commands inside substitution expressions. The wonderwhy-er/DesktopCommanderMCP project solves this through a character-by-character parser in the CommandManager class that treats command substitutions as separate parsing contexts, recursively extracting any commands they contain.

How extractCommands Processes $() Substitution

The $() command substitution syntax requires careful parenthesis balancing. In src/command-manager.ts, the parser detects this pattern and tracks nesting depth to find the correct closing delimiter.

// Lines 58-80 in src/command-manager.ts
if (char === '$' && i + 1 < commandString.length && commandString[i + 1] === '(') {
    const startIndex = i;
    let openParens = 1;
    let j = i + 2;                     // skip past `$(`
    while (j < commandString.length && openParens > 0) {
        if (commandString[j] === '(') openParens++;
        if (commandString[j] === ')') openParens--;
        j++;
    }
    if (j <= commandString.length && openParens === 0) {
        const subContent = commandString.substring(i + 2, j - 1);
        const subCommands = this.extractCommands(subContent);   // ← recursive call
        commands.push(...subCommands);
        i = j - 1;
        if (!inQuote) {
            continue;
        } else {
            currentCmd += commandString.substring(startIndex, j);
            continue;
        }
    }
}

The algorithm follows three critical steps:

  • Detection: Identifies the $( sequence to trigger substitution parsing
  • Balancing: Increments openParens for every ( and decrements for every ) until reaching zero
  • Recursion: Passes the extracted content back through extractCommands to discover any nested commands

When $() appears inside a quoted string, the parser preserves the original characters in currentCmd while still extracting the nested commands for blocklist validation.

How extractCommands Processes Backtick Substitution

Backtick substitution uses simpler delimiters—matching pairs of ` characters—making the parsing logic more straightforward but equally robust.

// Lines 82-100 in src/command-manager.ts
if (char === '`') {
    const startIndex = i;
    let j = i + 1;
    while (j < commandString.length && commandString[j] !== '`') {
        j++;
    }
    if (j < commandString.length) {
        const subContent = commandString.substring(i + 1, j);
        const subCommands = this.extractCommands(subContent);   // ← recursive call
        commands.push(...subCommands);
        i = j;
        if (!inQuote) {
            continue;
        } else {
            currentCmd += commandString.substring(startIndex, j + 1);
            continue;
        }
    }
}

Key characteristics of backtick handling:

  • Linear scan: Advances until the next backtick without nesting considerations
  • Same recursive treatment: Submits enclosed content to extractCommands for nested command discovery
  • Quote awareness: Maintains command string integrity when backticks appear within quoted contexts

Both substitution types ensure that commands hidden inside shell expansions are treated as independent commands subject to validateCommand checks against the configured blocklist.

Practical Command Extraction Examples

Basic $() Substitution

const raw = "echo $(rm -rf /tmp) && ls";
const cmds = commandManager.extractCommands(raw);
// cmds => ["echo", "rm", "ls"]

The parser extracts rm from inside the $() even though it appears as an argument to echo.

Basic Backtick Substitution

const raw2 = "git commit -m `date +%F`";
const cmds2 = commandManager.extractCommands(raw2);
// cmds2 => ["git", "date"]

The date command inside backticks is identified separately from the outer git command.

Deeply Nested Substitutions

const raw3 = "printf \"$(echo $(whoami))\"";
const cmds3 = commandManager.extractCommands(raw3);
// cmds3 => ["printf", "echo", "whoami"]

The recursive design handles arbitrary nesting depth, extracting whoami from the inner $(), echo from the outer $(), and printf from the top level.

Key Source Files

Summary

  • Character-by-character parsing: extractCommands walks input strings with full awareness of quoting, escaping, and substitution boundaries
  • Parenthesis balancing: $() handling counts nested parentheses to find correct delimiters
  • Linear backtick matching: ` syntax uses simple forward scanning to locate closing delimiters
  • Recursive extraction: Both substitution types submit their contents back to extractCommands, enabling unlimited nesting depth
  • Security integration: Extracted commands feed directly into blocklist validation, preventing circumvention through shell expansion

Frequently Asked Questions

Does the parser handle arbitrarily nested $() expressions?

Yes. The openParens counter increments for every ( encountered and decrements for every ), ensuring proper matching regardless of nesting depth. Each completed substitution recursively calls extractCommands on its contents.

What happens to backtick content if no closing backtick exists?

The parser verifies j < commandString.length before processing. If no closing backtick is found, the backtick character is treated as literal text and appended to the current command rather than triggering substitution extraction.

Are commands inside substitutions validated against the blocklist?

Absolutely. The entire purpose of recursive extraction is security validation. Every command discovered—including those nested inside $() or backticks—passes through validateCommand against the blocklist defined in config-manager.ts.

Why preserve substitution text when inside quotes?

The inQuote check preserves the original substitution syntax in currentCmd to maintain command string fidelity for execution, while still extracting the nested commands for security analysis. This dual approach ensures both accurate execution and comprehensive security coverage.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →