MCP Command Injection
A semicolon in a tool argument becomes a shell command.
What Is MCP Command Injection?
An MCP tool runs on a server, so every argument it receives is untrusted input. Build a shell command by pasting that input into a string and a semicolon or backtick stops being data: it becomes a command your server runs. You'll find the injected argument in an agent trace, watch the tool return an ordinary empty result while a root reverse shell carries out database credentials, then call git directly with no shell in the path. A code fix does not undo code that already ran.
What You'll Learn in MCP Command Injection
- Recognize that an MCP tool runs on a server, so any argument it uses to build a command is untrusted input that can carry code
- Explain how concatenating input into a shell command string turns metacharacters like ; and | into command syntax, producing remote code execution
- Understand that an injected command runs off-band, so a normal-looking tool response is no evidence that nothing happened
- Apply the durable fix: call the program directly and pass each value as a separate argument, with no shell to interpret it
- Distinguish parameterisation from escaping or blocklisting, and explain why removing the shell eliminates the category of bug rather than one instance
- Prove a code fix by replaying the original exploit, and recognize that a host which already ran attacker code must have its secrets rotated and be rebuilt
MCP Command Injection — Training Steps
-
A Tool That Runs Git
Sarnholt runs a public repo assistant. One of its tools, commit_search, looks up commits by author. Bob does not know how it is written, but a tool that searches git by a name he supplies is worth a probe. He opens the same agent anyone can use.
-
See It Work
First a normal request, to learn what the tool does with the name it is given.
-
The Name Ends Up Somewhere
The author he types comes back as a git query. Git is a command-line tool, so somewhere on the server that name is almost certainly being put into a git command. If it is put there as text, Bob can add to the command.
-
Close the Argument, Add a Command
Bob asks for commits by an author whose name is not a name. It ends the git command with a semicolon and appends his own: fetch a script from his host and run it.
-
The Boring Answer Hid the Damage
The assistant's reply is unremarkable: no commits matched. That is the point. The injected command does not report back through the tool, it runs quietly on the server.
-
A Shell on the Server
The script Bob's server handed back opened a connection straight to his listener. He switches to it to see what one commit_search call bought him.
-
Why It Worked
The tool did exactly what its code said to do. The question is what in that code turned an argument into a command.
-
A Shell Where There Should Be None
Alice is paged by the security team. The alert is not about a login or a file, it is about a process.
-
What the Alert Pins Down
The alert has narrowed it to one tool and one shape of input.
-
The Call in the Trace
The agent keeps a record of every tool call. The poisoned one is in it.
Security Framework Coverage
OWASP MCP Top 10
- MCP05:2025 Command Injection & Execution
CWE
- CWE-78 Improper Neutralization of Special Elements used in an OS Command
CIS Controls
- CIS 16 Application Software Security
NIST CSF
- PR.AT-02 Individuals in specialized roles are provided with awareness and training so that they possess the knowledge and skills to perform relevant tasks with cybersecurity risks in mind
- PR.PS Platform Security