Terminal Velocity: Bypassing MCP "Security" with CVE-2025-61492
Jan 9, 2026·5 min read
Executive Summary (TL;DR)
The developers of `terminal-controller-mcp` tried to secure their tool by blacklisting strings like "rm -rf". They failed to account for how shells actually work. By using command substitution and string concatenation, an attacker can construct any blacklisted command (e.g., `mkfs`) at runtime, completely bypassing the filter and executing arbitrary code with the privileges of the MCP server.
A Critical (10.0) command injection vulnerability in terminal-controller-mcp allows attackers to bypass a naive blacklist filter using basic shell obfuscation, granting full Remote Code Execution (RCE).
The Hook: Giving Robots Shell Access
The Model Context Protocol (MCP) is the new hotness, designed to let AI models interface directly with local tools and data. It's a great idea until someone decides to write a tool called terminal-controller-mcp. As the name implies, this tool literally gives an AI agent (and by extension, anyone controlling the prompt or the API connection) a direct line to your system's shell.
The developers weren't completely reckless. They knew that giving a robot raw access to /bin/bash might lead to accidental (or intentional) chaos. So, they implemented a security layer. A gatekeeper. A digital bouncer designed to stop the bad commands and let the good ones through.
Unfortunately, this bouncer is easily distracted by shiny objects and basic linguistics. Instead of implementing a secure execution environment, they relied on the oldest, most brittle defensive pattern in the book: the blacklist.
The Flaw: The Blacklist Fallacy
Root cause analysis often reveals complex memory corruption or race conditions. Here, the root cause is a fundamental misunderstanding of how command interpreters work. The developers implemented a check that looks for specific "dangerous" substrings within the user's input.
The fatal flaw is assuming that a command is defined by its static string representation. In a shell environment, a command is dynamic. cat /etc/passwd is the same as c''at /et??/pas*wd.
The application filters the input string, but the shell executes the interpreted result. If the input string doesn't contain the letters "m", "k", "f", "s" in that exact contiguous order, the filter passes it. But the shell, being helpful and flexible, allows us to construct that string on the fly using variables, substitution, or concatenation.
The Code: Pythonic Suicide
Let's look at the smoking gun in terminal_controller.py. This is the actual logic used to protect the system:
# The "Security" Logic
dangerous_commands = ["rm -rf /", "mkfs"]
def execute_command(command):
# Check if the command is naughty
if any(dc in command.lower() for dc in dangerous_commands):
return "For security reasons, this command is not allowed."
# Fire ze missiles!
# (Implied execution context passing this to os.system or subprocess with shell=True)
return run_shell(command)This code performs a case-insensitive substring search. If you type mkfs, you get blocked. If you type rm -rf /, you get blocked.
But notice what isn't blocked: echo, printf, $(), |, ;. By failing to sanitize the mechanism of execution itself (the shell meta-characters) and focusing only on specific payloads, the code creates an illusion of security that vanishes the moment you use a sub-shell.
The Exploit: Linguistic Gymnastics
To exploit this, we don't need buffer overflows. We just need to speak Bash. Let's say we want to run the forbidden command mkfs (Make Filesystem - a great way to ruin a server's day).
If we send mkfs, the Python in operator catches us.
Instead, we construct the word "mkfs" using echo and command substitution. The Python filter sees a bunch of echoes. The shell sees the instruction to build a command and run it.
The Payload:
echo "$($(echo -n m; echo -n k; echo -n f; echo -n s))"The Execution Flow:
- Python Filter: Does the string
echo "$($(echo -n m...containmkfs? No. Pass. - Bash Execution:
- Bash executes the inner sub-shell:
echo -n m; ...outputsmkfs. - Bash substitutes that result back into the command.
- The command becomes
mkfs. - Bash executes
mkfs.
- Bash executes the inner sub-shell:
This technique works for rm -rf / or any other blacklisted phrase. You can simply base64 encode your payload and pipe it to sh if you want to avoid all character filters entirely.
The Impact: Total Ownership
This is a CVSS 10.0 for a reason. There is no authentication required (assuming the MCP endpoint is exposed or the AI is tricked into using it), and the impact is total compromise of the user running the service.
In a containerized environment, an attacker now has a shell inside the container. From there, they can:
- Exfiltrate API keys or environment variables.
- Pivot to other services on the internal network.
- Perform container escapes if the container is privileged (which, ironically, tools like this often are to access the host filesystem).
For a developer running this locally to give their "Agent" power? The attacker just got a reverse shell on their laptop.
The Fix: Stop Using Shells
The mitigation is simple: Stop letting the shell parse the command.
1. Disable Shell Execution:
In Python, use subprocess.run with shell=False (the default) and pass the command as a list of arguments. This prevents shell expansion and command chaining.
# Safe(r) Implementation
subprocess.run(["ls", "-la"], shell=False)2. Whitelisting: If you must allow command execution, define a strict dictionary of allowed commands and their exact allowed arguments. Never trust a blacklist.
3. Input Validation:
Ensure arguments match strict regex patterns (e.g., ^[a-zA-Z0-9._-]+$). If you see a $, ;, or |, reject the request immediately.
Technical Appendix
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:HAffected Systems
Affected Versions Detail
| Product | Affected Versions | Fixed Version |
|---|---|---|
terminal-controller-mcp terminal-controller-mcp | <= 0.1.7 | TBD |
super-shell-mcp super-shell-mcp | <= 2.0.13 | TBD |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-77 (Command Injection) |
| Attack Vector | Network (JSON-RPC) |
| CVSS v3.1 | 10.0 (Critical) |
| Impact | Remote Code Execution (RCE) |
| Exploit Complexity | Low |
| Privileges Required | None |
MITRE ATT&CK Mapping
The software constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.
Subscribe to updates
Get the latest CVE analysis reports delivered to your inbox.