Vulnerabilities
Claude Agent SDK flaw: a session ID that runs commands
CVE-2026-96620 lets a dash-prefixed session ID inject CLI flags into the Python Claude Agent SDK and run commands. Fixed in 0.2.121. How to find and fix it.
CVE-2026-96620, published on October 16, is an argument injection flaw in the Claude Agent SDK for Python, the claude-agent-sdk package on PyPI that teams use to build agents on top of the Claude Code command-line interface (CLI). Before version 0.2.121, the SDK handed the resume option to the CLI without checking it. A session ID that starts with a dash was read as a new command-line flag, and an attacker could use that to start a malicious Model Context Protocol (MCP) server and run commands on the host, outside the SDK's permission controls.
The flaw only matters if your application lets a user, a request or another system choose the session ID it resumes. That is a common design in multi-user agent backends, where each user's conversation has its own session and the ID travels in a URL or API body. The GitHub advisory rates it Critical, 9.2 under CVSS 4.0 (the Common Vulnerability Scoring System). There is no confirmed exploitation and no public proof of concept so far. If your agents resume sessions by ID, upgrade and add validation this week.
How it works
The Python SDK does not talk to the model directly. It starts the Claude CLI as a subprocess and builds that process's argument list from the options you set in ClaudeAgentOptions. Anthropic's session documentation describes resume as the way to return to a specific past session, for example "one per user in a multi-user app", using the ID read from ResultMessage.session_id on an earlier run.
Before 0.2.121, the SDK added --resume and the session ID as two separate arguments. Command-line parsers decide what each argument means by its shape, and an argument that begins with a dash is an option, not a value. So when the session ID itself started with a dash, the CLI never saw it as the ID to resume. It parsed it as a fresh flag of the attacker's choosing.
The flag that turns this into code execution is --mcp-config. MCP is the protocol agents use to reach outside tools, and a stdio MCP server is defined by a command the CLI launches as a local process and talks to over standard input and output. An injected config can name any command, and the CLI runs it at startup. This happens before the agent loop begins, so the SDK's tool permissions, allowed tools lists and permission callbacks never get a say.
The advisory lists three weaknesses: CWE-20 (improper input validation), CWE-88 (argument injection) and CWE-78 (OS command injection). The CVSS 4.0 vector is AV:N/AC:L/AT:P/PR:N/UI:N with high impact on confidentiality, integrity and availability. AT:P records the precondition: the application must pass attacker-controlled input to resume. Where it does, no account and no user interaction are needed. One tracker, The Hacker Wire, still showed the CVSS score as pending analysis when we checked; OSV, OpenCVE and Vulert all list 9.2.
The fix in 0.2.121 (pull request #1123) passes --resume and --session-id as single joined tokens, such as --resume=<value>, so a value with a leading dash stays bound to its flag. The same release hardened the SDK's build scripts against command injection through the CLAUDE_CLI_VERSION variable, a separate issue.
What attackers are doing
Nothing confirmed. OpenCVE and The Hacker Wire both show the CVE outside the CISA Known Exploited Vulnerabilities (KEV) catalog, and The Hacker Wire and SecurityOnline's CVE Watchtower report no confirmed exploitation and no public exploit code. The EPSS (Exploit Prediction Scoring System) estimate is about 0.71% for the next 30 days.
The issue was reported through HackerOne by the researcher moamenmahmood. The fixed release was on PyPI in July 2026, months before the CVE was published on October 16. Any service rebuilt since then with an unpinned SDK already has the fix. Pinned versions and long-lived images are where old copies remain.
The technique needs no special tooling once someone finds an endpoint that forwards a session ID, so a low EPSS figure is no reason to leave it unpatched.
What to do
1. Find the SDK and the call sites. In each virtual environment, container image and CI runner, run pip show claude-agent-sdk and read the Version line. Check lockfiles too, since images built months ago may hold an older copy: grep -rn "claude-agent-sdk" requirements*.txt pyproject.toml poetry.lock uv.lock. Then find where the session ID is set with grep -rnE "resume\s*=" --include=*.py . and trace each value back. If it comes from a query string, a request body, a chat message, a webhook or a database field a user can write, that service is exposed.
2. Upgrade. Run pip install --upgrade "claude-agent-sdk>=0.2.121", set claude-agent-sdk>=0.2.121 as the floor in your requirements or pyproject.toml, and rebuild every image that bundles the package. The advisory covers the Python package only.
3. Validate the ID anyway. Session IDs the SDK returns are UUIDs, such as 5b3f2c1a-8d4e-4f6b-9a7c-2e1d0f9b8a6c in Anthropic's docs. Parse the input and pass on only the canonical form:
import uuid
def safe_session_id(value):
try:
return str(uuid.UUID(value))
except (ValueError, TypeError, AttributeError):
raise ValueError("invalid session id")
The canonical string is hex digits and hyphens and can never begin with a dash. This is also the workaround for anyone who cannot upgrade today. Then check that the session belongs to the user asking for it. Without that check, a well-formed UUID still lets one user resume another user's conversation.
4. Check whether you were hit. Search request and application logs for session ID values that start with -, or that contain mcp-config. On hosts with process auditing (auditd, Sysmon for Linux or an EDR), look for Claude CLI processes started with an --mcp-config argument your code does not set, and for child processes of the CLI that are not your configured MCP servers. If you find either, treat the host as compromised: rotate the API keys, cloud credentials and tokens the agent's environment could read, and rebuild it from a clean image.
Flags are input too
Many agent frameworks drive a CLI as a subprocess, as this SDK does, and every option that ends up in an argument list is a place where data can turn into instructions. The SDK's own fix shows the two habits worth copying in your code: bind values with --flag=value, and validate anything that has a known shape before it reaches the subprocess. Where the wrapped tool supports it, put -- before positional arguments so nothing after it is parsed as an option.
Review your own wrappers with the same eye. Any field your code passes through to the CLI, including the SDK's extra_args, should never carry user input. A shell-free subprocess call stops shell metacharacter injection, but it does nothing about a value that the target program reads as a flag.
If you run agents in production and want this kind of input path reviewed, our red team code review traces user-controlled data into subprocesses and tool calls, and our AI-native systems work covers building agent backends with these checks in place. Open the chat and Yaali, our AI agent, will pass your question to an engineer.
Sources: GitHub advisory GHSA-h4mw-j7qp-8mwm, claude-agent-sdk-python v0.2.121 release notes, Pull request #1123, OSV, OpenCVE, Vulert, The Hacker Wire, SecurityOnline CVE Watchtower, NewReleases: v0.2.121, Claude Agent SDK sessions documentation.