Skip to content

interact_with_process bypasses blockedCommands — missing validateCommand() call in interactWithProcess() #552

Description

@nobugpal

Summary

The interact_with_process MCP tool does not call commandManager.validateCommand() before sending input to a running process. This means the blockedCommands configuration has zero effect when commands are injected through an interactive process.

How This Differs from Known Limitations

Your SECURITY.md correctly notes that command blocking can be bypassed via substitution and absolute paths (CVE-2025-11490, CVE-2025-11491). Those are bypass techniques against a validation function that does run.

This finding is different: interactWithProcess() has no validation call at all. The startProcess() function correctly calls commandManager.validateCommand() at line ~103 of improved-process-tools.ts, but interactWithProcess() — which sends arbitrary input to running processes — completely skips this step.

Attack Chain

1. start_process("python3 -i")             → ALLOWED
2. interact_with_process(pid,
     "import os; os.system('dd if=/dev/zero of=/tmp/pwned bs=4 count=1')")
   → NO validation check
   → dd executes successfully (EXIT: 0)

All 27+ blocked commands (sudo, dd, mkfs, iptables, etc.) are reachable through this path.

Reproduction

Reproduced in Docker with the recommended Docker setup:

docker run --rm -it node:20-bookworm-slim bash
apt-get update && apt-get install -y python3
mkdir /app && cd /app
npm init -y && npm install @wonderwhy-er/desktop-commander@0.2.43

Then run the assessment script (attached below or available on request).

Verification Output

start_process → dd if=/dev/zero of=/tmp/test bs=4 count=1
  Response: Error: Command not allowed       ← Correctly blocked

interact_with_process → Python os.system("dd if=/dev/zero...")
  Response: 1+0 records in, 1+0 records out, EXIT: 0
  File written: /tmp/BYPASS-*               ← Bypassed

Root Cause

File: src/tools/improved-process-tools.ts

  • startProcess(): ✅ has await commandManager.validateCommand(parsed.data.command) at line ~103
  • interactWithProcess(): ❌ no call to commandManager.validateCommand()

The fully functional validation method exists in src/command-manager.ts — it handles path normalization, command substitution detection ($(), backticks), and multi-command parsing. It is simply not wired into interactWithProcess().

Suggested Fix

One call in interactWithProcess() before sending input:

if (parsed.data.input) {
  const isValid = await commandManager.validateCommand(parsed.data.input);
  if (!isValid) {
    return { content: [{ type: "text", text: "Input rejected: contains blocked command." }], isError: true };
  }
}

I recognize the project's stated position that these restrictions are guardrails rather than a security boundary. The inconsistency between start_process (enforced) and interact_with_process (unchecked) is worth noting because users who configure blockedCommands based on start_process's behavior may not realize the gap.

Happy to discuss further. Attribution: nobugpal (if desired).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions