The Incident
CVE-2026-96620 is an argument-injection vulnerability in Anthropic’s Python claude-agent-sdk — the first-party SDK developers build Claude agents on top of. All versions below 0.2.121 are affected; 0.2.121 is the fix. The CNA-published CVSS v4.0 base score is 9.2, with CWE assignments CWE-20 (improper input validation), CWE-78 (OS command injection), and CWE-88 (argument delimiters in a command). The bug lives in how the SDK composes argv for the bundled claude CLI it spawns: the SDK passed --resume and the session-ID value as two separate argv tokens, and the CLI declares --resume with an optional value, so a session ID starting with - is parsed as its own sibling flag instead of as the argument to --resume.
The exploitation shape named in the advisory is --mcp-config. An attacker who can route untrusted input into the resume keyword argument of ClaudeAgentOptions — for example, a session identifier pulled from a URL path, an API request, or a redelivery queue — can prepend a dash and then an attacker-chosen flag. --mcp-config tells the Claude CLI to attach a stdio MCP server, which spawns the server’s command at CLI startup. That command runs under the SDK process’s own authority, before the SDK’s permission layer gets a chance to inspect anything. The precondition is narrow (an application must pass untrusted input to resume) but the resulting authority gap is total: permission_mode, allowed_tools, the canUseTool callback, and hooks all govern tool calls once the agent is running — they do not govern what the CLI launches on the way up.
The patch moves --resume to the =-joined single-token form --resume=<value>, which binds the value lexically and makes it unparseable as a separate flag. The same change applies to --session-id. No in-the-wild exploitation of CVE-2026-96620 has been publicly reported, but the related class is already live: CVE-2026-105697, a Langflow MCP stdio command-injection reached by a different mechanism and fixed in 1.10.3, has been reported as exploited in the wild. CVE-2026-47751 in claude-code-action (before 1.0.74) is a sibling chain — a malicious .mcp.json committed to a pull request made the Claude Code Action runner execute attacker code before the permission layer saw it. Three injection paths, one attach primitive.
The Authority Path That Failed
The identity carrying execution authority is the claude CLI subprocess the SDK spawns on the operator’s behalf. Its intended held scope is the one the operator wrote into ClaudeAgentOptions: a chosen permission_mode, an allowed_tools allowlist, a canUseTool callback for interactive approvals, hooks for pre- and post-tool observation, and the specific MCP servers the operator decided to attach. The scope the subprocess actually held, when the bug fires, additionally includes a stdio MCP server defined by the attacker’s --mcp-config argument. That extra server’s tools and side effects materialize at CLI startup — the SDK’s permission layer runs later, against the tool-call stream, not against the process-launch stream.
The trust anchor that failed first is argv composition. The SDK’s _build_command() used the two-token form (cmd.extend(["--resume", value])), which lets the CLI parser treat a flag-shaped value as a separate flag. The permission framework was never bypassed on its own terms; the bug attached an unapproved execution path in a lifecycle phase the permission framework does not govern. The gap between held and exercised authority was discoverable in advance: a tool-manifest check that resolves which MCP servers an agent attaches at process start, cross-referenced with every source that feeds the resume/session-id arguments, would have surfaced the path from “attacker-chosen string” to “stdio MCP server spawned at startup” without needing to see a specific payload. The pattern is the same shape as the Langflow CSV Agent + Python REPL reference: a documented permission layer that an operator reasonably trusts, undermined by a load-order gap that attaches a code-execution path before permissions run.
SecurityV0 Perspective
For any application built on claude-agent-sdk < 0.2.121 that forwards external input into resume, SecurityV0 raises unproven_execution against the agent runtime. The finding attaches because the resolved process-launch argv can introduce an MCP server that the deploying operator never authorized, and because the ingestion channel supplying resume (URL path, API body, queue redelivery) is one the operator never signed off as a source of execution capability. The secondary structural kin — claude-code-action before 1.0.74 reached by .mcp.json in a pull-request diff, and Langflow MCP stdio before 1.10.3 reached by configuration-string composition — are not the same bug, but they are the same finding: MCP server configuration is becoming the ecosystem-wide “attach arbitrary execution” primitive, reached by whatever injection path the surrounding wrapper leaves open.
The evidence pack would carry the pinned claude-agent-sdk version, the operator-authored ClaudeAgentOptions (declared permission_mode, allowed_tools, hook registrations, declared mcp_servers), the full launch-time argv for every claude CLI invocation, and the ingestion lineage of every string that reaches resume or session_id. Before an incident, that pack answers: for each agent task, do the MCP servers attached at process start match the operator-authored allowlist, and is any attacker-reachable source feeding resume or session_id? Post-exfiltration, it answers: for a specific task instance, which argv token introduced the MCP server that executed, what command did that server spawn, and what did the operator record as authorized at that moment.
What To Do
- Upgrade
claude-agent-sdkto 0.2.121 or later and pin it. The fix is the switch to--resume=<value>and--session-id=<value>single-token forms in_build_command(). Pin the version in your lockfile and verify at CI time; the SDK installs the matching Claude CLI, so an SDK upgrade is the control point. Audit any wrapper you ship on top of the SDK that re-composes argv — the same gap re-opens if a wrapper re-splits the resume argument. - Treat
resumeandsession_idas untrusted command tokens, not identifiers. Validate at the application boundary with a strict character allowlist (hex, URL-safe base64, UUIDs). Reject any value starting with-. Do this even after upgrading, becauseextra_argsstill composes two-token flags today and because the same mistake is easy to re-introduce in a sibling CLI wrapper. - Resolve the full process-launch argv for every agent task and compare it to the operator-authored manifest. The SDK permission layer runs against tool calls; it does not run against argv. Capture the realized argv (every
--mcp-config, every--allowed-toolsoverride, every--permission-mode) at the moment the CLI starts, and alert when the attached MCP servers at startup are not a subset of the operator’s declaredmcp_servers. - Audit sibling MCP-attach primitives in your stack. CVE-2026-47751 reaches the Claude Code Action runner through a
.mcp.jsoncommitted to a pull-request diff; CVE-2026-105697 reaches Langflow through MCP stdio configuration. For every path an agent reads MCP configuration from — argv, repository files, environment variables, inbound API — verify whether that path is attacker-reachable and whether a permission check gates the attached servers before they execute. - Keep the permission framework, and move the gate earlier.
permission_mode,allowed_tools,canUseTool, and hooks remain the right contract for in-task tool calls; they are not sufficient on their own because an attach primitive runs before them. Add a pre-launch policy check that reads the composed argv and the resolved MCP servers and rejects any configuration the operator did not author.
Sources
- NVD — CVE-2026-96620
- GitHub Security Advisory GHSA-h4mw-j7qp-8mwm
- Pull Request #1123 — Pass —resume and —session-id as a single argv token
- claude-agent-sdk-python v0.2.121 release notes
- OSV.dev — CVE-2026-96620 (pip:claude-agent-sdk)
- NVD — CVE-2026-47751 (claude-code-action .mcp.json)
- SentinelOne vulnerability database — CVE-2026-105697 (Langflow MCP stdio)