
Documents CVE-2026-52618 with a PoC for OS command injection in @webfer/mcp-ansible-drupal via executeDeployment extraVars, plus detection guidance and mitigations.
executeDeployment extra varsspawn(cmd[0], cmd.slice(1), { shell: true }) re-joins an argument array into a
single /bin/sh -c string without escaping, so a shell metacharacter in an
extraVars value supplied to the executeDeployment MCP tool executes on the
host running the server.
| CVE | CVE-2026-52618 |
| CWE | CWE-78 (Improper Neutralization of Special Elements used in an OS Command) |
| Package | @webfer/mcp-ansible-drupal (npm) |
| Affected | 2.0.0 |
| Fixed in | 2.0.3 (published 2026-06-01) |
| CVSS v3.1 | AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H = 8.4 High (stdio transport). AV:N → 9.8 Critical if the server is wrapped in a network transport. |
| Reported by | s1ko (github.com/s1ko, [email protected]) |
| CVE assigned | 2026-07-13, MITRE CNA-LR |
This issue is fixed. Upgrade to 2.0.3 or later.
MCP tool arguments are attacker-influenced. They are produced by an LLM from
the content the agent processes — documents, web pages, tool output — so
indirect prompt injection puts an attacker in control of the values a tool
handler receives. A handler that feeds any of those values into a shell turns
that influence into code execution on the host running the MCP server. This is
the same model as the precedent case aws-mcp-server / CVE-2026-5058.
The consequence is that "the caller is a trusted LLM" is not a mitigation. Tool arguments are untrusted input.
src/helpers/runAnsible.ts in 2.0.0 built an argument array and then ran it
with the shell enabled:
const proc = spawn(ansibleCmd[0], ansibleCmd.slice(1), { shell: true });
With shell: true Node does not execute ansibleCmd[0] with the remaining
elements as argv. It joins the whole array into one string and hands it to
/bin/sh -c, with no quoting or escaping applied to the elements. Any element
carrying ;, &&, |, backticks or $() therefore breaks out of the intended
command.
The reachable path is the executeDeployment MCP tool
(src/tools/executeDeployment.ts), which forwards caller-supplied extraVars
into the array as --extra-vars key=value. A value of ; touch /tmp/marker ;
yields:
/bin/sh -c "ansible-playbook -i inv.ini play.yml ... --extra-vars deploy_env=; touch /tmp/marker ; echo INJECTED"
A secondary sink in the same version interpolated projectRoot into
execSync('ansible-vault encrypt ' + vaultPath).
poc/driver.mjs calls runAnsible directly with a crafted
extraVars value and checks for the marker file. ansible-playbook does not
need to be installed — the injected command runs in the same /bin/sh -c
string regardless of whether the leading binary resolves.
npm pack @webfer/[email protected] && tar xf webfer-mcp-ansible-drupal-2.0.0.tgz
node poc/driver.mjs
Expected on an affected version:
{"target":"@webfer/mcp-ansible-drupal 2.0.0","sink":"runAnsible -> spawn(cmd[0], cmd.slice(1), {shell:true})","vector":"executeDeployment extraVars value","marker":"/tmp/PWNED_ansible","created":true,"verdict":"CONFIRMED - command injection executed"}
Validated 2026-06-01 in an isolated container and re-validated 2026-06-13 with
a benign touch marker. Run it only against infrastructure you are authorized
to test.
--extra-vars value reaching ansible-playbook that contains ;, &&,
||, |, a backtick or $(. Legitimate Ansible variable values essentially
never do.sh -c process whose command line contains ansible-playbook and a
second command after a separator — a process-tree signal (auditd, eBPF,
Falco, EDR) that does not depend on application logging.ansible-playbook, git and
ansible-vault are expected; a shell, an interpreter or a network client is
not.executeDeployment arguments contain shell
metacharacters. Log tool arguments — most MCP deployments do not, and it is
the only place the injected value is visible in application terms.MITRE ATT&CK T1059.004 Command and Scripting Interpreter: Unix Shell.
Upgrade to @webfer/mcp-ansible-drupal 2.0.3 or later. The current source
calls spawn(ansibleCmd[0], ansibleCmd.slice(1), { … }) with no shell option,
so the array is passed as argv and metacharacters lose their meaning.
The general rule for MCP tool handlers, and what the fix applies here:
shell: true when an argument array is already available. The
array form exists precisely to avoid the shell.execFile/spawn with an explicit argument vector.z.string() proves the value is a
string and nothing about what is in it. Allowlist where the value set is
known; reject shell metacharacters where it is not.NIST SP 800-53r5 SI-10; OWASP ASVS v4 §5.3.8; CWE-78 mitigations M1 and M2.
| Date | Event |
|---|---|
| 2026-06-01 | Vulnerability dynamically validated against 2.0.0 |
| 2026-06-01 | 2.0.3 published to npm with the shell: true removed |
| 2026-06-13 | Re-validated on a second host |
| 2026-07-13 | MITRE CNA-LR assigns CVE-2026-52618, s1ko credited as discoverer |
| 2026-08-22 | This write-up published |
aws-mcp-server)Companion advisories from the same research pass: CVE-2026-52616, CVE-2026-52617.
MIT — see LICENSE.