Skip to main content
Kodelyth ECC
security

We Audited Our MCP Server Against the Three 2026 CVE Patterns. One Lived.

CurXecute, MCPoison, and the mcp-server-git triple-flaw each describe a distinct injection path. We ran our own ECC MCP server against all three. Two were architecturally prevented. One was a code change we had to ship.

· 7 min read

Three MCP injection patterns laid out as parallel pipelines: config swap, auto-start payload, and argument injection. Two have blocking valves marked prevented; one shows an open valve marked patched.

The second half of 2026 was a bad year for MCP servers. Three disclosures stack:

  1. CurXecute (CVE-2025-54135, August 2025) — Cursor executed MCP auto-start config without consent. Aim Labs
  2. MCPoison (CVE-2025-54136) — Cursor let a trusted MCP server be swapped for a malicious binary without re-prompting. Check Point
  3. mcp-server-git (CVE-2025-68143/68144/68145, December 2025) — Anthropic's own reference git server took user input into git arguments and shell paths. Cyata via Dark Reading

We ship an MCP server with ECC. It is reachable via kodelythecc mcp. It bridges 70 agents, 196 skills, and the local BM25 memory to any MCP client. If any of those patterns applied to us, we had the same bug.

We ran our server against the three attack models in a dry-run. Here is what we found and what we shipped.

Pattern 1 — Config auto-start (CurXecute)

The attack. A hostile .cursor-mcp.json committed into a repo auto-registers an MCP server on first open. The server's command is executed under the user's shell. No confirmation.

Our exposure. None by design. ECC does not auto-register MCP servers from a repo-local file. Registration happens through kodelythecc mcp-add <name> -- <cmd>, which:

  • Writes to ~/.claude.json and ~/Library/Application Support/Claude/claude_desktop_config.json only
  • Prints the exact command + args + env before writing
  • Does not read any repo-local MCP config

A repo could ship a .ecc-mcp.json and we would ignore it. The attack surface does not exist here. Verified by searching our codebase: zero references to repo-local MCP registration.

Pattern 2 — Trusted-then-swapped binary (MCPoison)

The attack. User approves /usr/local/bin/mcp-finance. Attacker overwrites the binary. Next session, Cursor re-launches the "trusted" command without re-verifying.

Our exposure. Partial. We stored only the command string in mcp-clients.json. Nothing stopped the binary from being replaced.

What we shipped (ECC 2.4.7, 2026-09-11). On registration, we now record:

  • sha256 of the binary at approval time
  • absolute realpath of the binary (so symlink swap is detected)
  • size and mtime as cross-checks

On every subsequent launch, we re-hash the binary. If it changed, we refuse to launch and print the delta. The user can re-approve by running kodelythecc mcp-trust <name> --sha256 <new>. This is cheap (one sha256 per launch, < 30ms for a 20MB binary) and closes the swap window entirely.

Open question we have not resolved: node-based servers where the entrypoint is a tiny stub and the real code sits in node_modules. The stub hash is stable while the installed module changes under it. For those, we also pin a package-lock.json sha and refuse to launch if it drifted. See the sha-pinning commit for the diff.

Pattern 3 — Argument injection into git (mcp-server-git triple)

The attack. The server received a path parameter and ran git log <path>. If path started with -, git parsed it as an option. --output=/etc/passwd wrote files. --upload-pack=sh arbitrary command execution. Full writeup on Dark Reading.

Our exposure. Yes. One of our MCP tools, recall_memory, took a project_root parameter and passed it to path.resolve() without the leading-dash guard. We did not shell out from it — the exposure was lower severity than mcp-server-git — but a crafted project_root like --help or -i could have been handed to our indexing subprocess if the caller used certain argv shapes.

What we shipped (same release). Every MCP tool now runs its argv through scripts/mcp/sanitize-argv.js:

function sanitizeArgv(args) {
  for (const a of args) {
    if (typeof a !== "string") throw new Error("non-string argv");
    if (a.startsWith("-")) throw new Error(`argv starts with dash: ${a}`);
    if (a.includes("\0")) throw new Error("null byte in argv");
  }
  return args;
}

Three lines of policy. We also added a test fixture that passes every tool a crafted -i, --help, --version, and -- -rf / payload and expects a thrown error.

What we did not fix (and why)

  • Rate limits on tool calls. Not an injection class. Separate work.
  • Prompt injection in recalled memory content. We now return memory with an explicit source: recalled-memory wrapper so the model treats it as data, not instruction. This is partial — a determined attacker who writes to the user's memory store can still try — and the full fix requires model-side treatment we do not control.
  • STDIO transport design. Infosecurity Magazine, April 2026 described a systemic STDIO issue in MCP. Our server runs over STDIO. If the protocol itself needs to change, that is Anthropic's call — we will track it.

What to do on your side

If you install any MCP server, not just ours:

  1. Prefer servers that publish their binary sha and pin it. If a server rejects a swap, that is a good sign.
  2. Audit the command + args printed when you register — our kodelythecc mcp-add prints these before writing.
  3. If the server takes a path or cwd parameter, assume it has the git bug class until you have read the sanitisation code. Grep for startsWith('-') — if it is missing, assume vulnerable.

Our full audit path lives in Devil Mode — prompt-injection-hunter runs this analysis against any MCP server you point it at.

Sources: