CVE-2026-0968: The libssh Heap Read That Isn’t as Scary as Scanners Say
A missing null check in libssh’s SFTP directory listing code lets a malicious server crash...
Apr 21, 2026
A developer clones a repository and opens it in VS Code at 10:47 a.m. Before their cursor blinks, six different configuration file formats on disk have a chance to execute shell commands on the host. A .vscode/tasks.json with runOn: folderOpen. A .devcontainer/devcontainer.json with initializeCommand. A post-checkout hook already sitting in .git/hooks/. A postinstall line waiting in package.json for the next dependency install. A .envrc in the project root. A Makefile the Makefile Tools extension is about to auto-configure.
None of this is MCP. All of it has been this way for years.
Last week, a security vendor published a report calling a related pattern “the mother of all AI supply chains.” Of all the config-to-execution surfaces on a modern developer machine, they picked the one with Anthropic’s name on it.
Credit where it’s due: the research is real. OX Security’s team demonstrated that the STDIO transport in Anthropic’s Model Context Protocol SDKs accepts a command string via StdioServerParameters and hands it to the operating system to execute, without validating that the target is actually an MCP server. Pass a malicious command, get an error back, but the command still ran. They shipped ten-plus Critical and High CVEs across LiteLLM, LangFlow, Flowise, Windsurf, Cursor, GPT Researcher, and others. They demonstrated zero-click prompt injection to local RCE in Windsurf (CVE-2026-30615). They showed poisoned entries making it into nine of eleven MCP registries they tested.
Anthropic declined to patch the protocol, calling the behavior expected and placing sanitization responsibility on implementers. OX called that response “Fault-Diversion” and published anyway.
The CVEs are real. The downstream projects genuinely did have bugs. The framing is where this gets interesting.
Strip the rhetoric, and the report’s thesis reduces to a single sentence:
A JSON configuration file contains a field whose value is interpreted as a command.
That’s the “architectural flaw.” A schema with a command field, parsed by an SDK, passed to exec.
On that definition, the set of “vulnerable” software includes Git, npm, pnpm, Yarn, setuptools, pip, pytest, Python’s site module, IPython, Jupyter, VS Code, Cursor, Windsurf, Docker, Docker Compose, direnv, GNU Make, just, Task, GitHub Actions, GitLab CI, CircleCI, and every Dev Container specification on Earth. Most of them have had this “flaw” since before MCP existed.
None of them has had a 150-million-download number attached to a whitepaper about it. Then again, none of them have Anthropic in the title.
kernel.json, the twinJupyter scans a set of well-known directories for JSON files with this shape:
{
"argv": ["python3", "-m", "ipykernel", "-f", "{connection_file}"],
"display_name": "Python 3",
"language": "python",
"env": { }
}
A JSON file, sitting in a filesystem-scanned directory, with an argv array and an env dict, launched by the host on kernel start without validation of what’s in argv[0]. The spec even carries nearly the same caveat about env that MCP does.
This has been the kernelspec format since roughly 2015. Every data scientist, every ML researcher, every CI pipeline that spins up a kernel depends on it. No one has ever called it a critical systemic vulnerability. If OX’s framing is correct, the entire Jupyter ecosystem has been running an unpatched RCE for a decade.
.devcontainer/devcontainer.json, no trust gate at allOpen a repository in Cursor or VS Code with a Dev Container configuration, and the field initializeCommand runs shell code on the host, not inside the container. The official spec carries a warning emoji next to it: “⚠️ The command is run wherever the source code is located on the host.”
There is no Workspace Trust gate for initializeCommand. Opening a folder is enough. This is materially worse than the MCP STDIO pattern OX describes — MCP at least requires a per-server approval and a client restart — and it has been shipping in the default VS Code experience for years.
~/.docker/cli-plugins/ — one file, zero promptsDrop an executable named docker-anything into ~/.docker/cli-plugins/ and the Docker CLI will invoke it as a subcommand on the next docker anything …. No signature, no checksum, no allow-list, no trust dialog. docker compose itself ships this way.
For an AI agent with write access to the home directory, this is one file and a chmod +x away from persistence that fires the next time the developer runs docker buildx or docker compose up. It is strictly less friction than editing an MCP config, which typically requires a client restart and sometimes surfaces a dialog. There is a 2025 local privilege escalation CVE (GHSA-p436-gjf2-799p) confirming the surface, and no one has written a mother-of-all-supply-chains report about it.
.vscode/tasks.json with runOn: folderOpenA task with runOptions.runOn: "folderOpen" and presentation.reveal: "never" runs automatically and silently when a folder opens. Microsoft explicitly labels this as-designed. DPRK’s “Contagious Interview” campaign has been using it in the wild to pwn developer machines during fake job interviews. Workspace Trust is the stated mitigation — which means once the developer clicks “Trust,” every subsequent malicious commit to that repo fires silently.
package.json postinstall and the Shai-Hulud wormA single line in package.json:
"postinstall": "node bundle.js"
That is a string in a JSON file which the npm CLI passes to a shell. It is what the Shai-Hulud worm used in September and November 2025 to self-propagate across 795+ packages including packages from Zapier, PostHog, and Postman. Credential harvesting, GitHub token theft, auto-publishing malicious versions.
OX’s own report cites npx/npm args as the “allowlist bypass” in one of their CVEs. That is the joke writing itself: npx is the bypass for every allowlist ever written, because executing arbitrary packages is its literal job description.
CVE-2024-32002.git/hooks/post-checkout is a shell script. Git runs it. That’s not a vulnerability. That’s Git.
CVE-2024-32002, CVSS 9.0, was a vulnerability: a crafted repository with a submodule-symlink race condition could write into .git/hooks/ during recursive clone, turning git clone into RCE. The distinction matters. The hooks directory behaving as a hooks directory is not the flaw. Input crossing a trust boundary to reach it is.
This is the same distinction the MCP conversation deserves and isn’t getting.
There is a reason the report is titled after Anthropic and not after Project Jupyter, Microsoft, Docker Inc., the npm registry, or Git. Anthropic is the company everyone is writing about right now, and “critical flaw at the core of [hot AI company]” is a headline that writes itself. “Critical flaw at the core of IPython kernelspec parsing” does not. The research is competent. The naming is a marketing choice. Both things are allowed to be true.
The actually interesting question isn’t whether config-to-exec qualifies as a vulnerability. It’s how strong the trust gate is on each surface.
| Surface | Trust gate | Strength |
|---|---|---|
direnv .envrc | Content-hash-pinned direnv allow | Strong — any edit re-prompts |
VS Code tasks.json | Workspace Trust, one-time per folder | Medium, trust-then-forget |
| MCP STDIO (Claude Desktop, Cursor) | Per-server approval plus client restart | Medium |
npm postinstall | --ignore-scripts flag, opt-in per package manager | Weak on npm, stronger on pnpm v10+ |
devcontainer.json initializeCommand | None | None |
~/.docker/cli-plugins/ | None | None |
MCP’s trust gate is in the middle of the pack. The surfaces worth being genuinely alarmed about are the ones at the bottom, initializeCommand and cli-plugins/, and neither of them has produced a 150-million-download press cycle.
The right model for this conversation is direnv. A config file whose contents become shell, gated by a content hash: approve once, edit re-prompts, no drift. MCP could adopt something like it. So could everything else in the table. That would be a useful research direction. “Anthropic’s architecture is the mother of all AI supply chains” is not.
Here is the part of the ambient MCP anxiety that is real and worth stating plainly.
Every config-to-exec surface in the developer toolchain was designed around one assumption: a human reviews the file before the tool runs it. tasks.json, devcontainer.json, package.json, .git/hooks/, kernel.json, Docker contexts — all of them. That assumption survives because humans edit these files, and humans have slow hands and narrow attention.
LLM coding agents violate the assumption. An agent can write .devcontainer/devcontainer.json, drop a file in ~/.docker/cli-plugins/, append a line to package.json, or create .cursor/mcp.json in response to a prompt injection delivered through a webpage, a dependency README, a Jira comment, or a search result. This is Simon Willison’s lethal trifecta: private data, untrusted content, and external communication, and it applies to every row in that table above, not just the MCP row.
The honest version of OX’s thesis: in a world of ambient prompt injection, every config-to-exec surface in the developer toolchain needs a stronger trust model. That’s correct. It’s also an ecosystem problem, not an Anthropic problem.
Don’t wait for a standards body to decide what “vulnerability” means.
.vscode/, .devcontainer/, .cursor/, and .git/hooks/ on every clone.--ignore-scripts and explicit build opt-ins. If you’re still running default npm on untrusted repos, MCP is not your most immediate problem.~/.docker/cli-plugins/ and ~/.docker/contexts/ on a schedule. No scanner checks these by default, and they are two of the cleanest persistence primitives on a developer machine..git/hooks/ or ~/.docker/.execve() lineage at runtime. A JSON-parsing process spawning a shell is a signal, regardless of which configuration format invited it. This is what runtime detection is for, and it does not care what the security community decides to call a vulnerability this week.Is MCP STDIO a vulnerability? No more and no less than git clone, npm install, or opening a folder with a .devcontainer in it. Whether any of these count as “vulnerabilities” is a framing choice, not a technical fact.
Should I disable MCP? No. Treat MCP servers the way you treat npm dependencies: install only from sources you trust, review what they do, don’t give them credentials they don’t need, and don’t expose the services they connect to on public networks.
How is this different from tasks.json? It mostly isn’t. Both are JSON files with fields that become commands. MCP at least requires per-server approval and a client restart. tasks.json requires one “Trust this workspace” click, then runs silently forever.
What should I actually worry about? Prompt-injectable AI agents with write access to any config-to-exec surface on your machine, and no runtime visibility into the process lineage that follows when those configs get parsed. That’s the real problem. It’s bigger than MCP and it won’t be solved by an SDK-level patch.
Why did OX pick MCP specifically? The research is solid. The title of the report is a marketing choice. Both are allowed.
A missing null check in libssh’s SFTP directory listing code lets a malicious server crash...
We just published a deep breakdown of the Trivy supply chain attacks yesterday. Twenty-four hours...
We’ve been going back and forth on whether to publish this post. As the maintainers...