AWS Shipped the Fix in August and the Advisory in October — CVE-2026-97662 Turns a Git Ref Into a File Write
AWS published security bulletin 2026-121-AWS on 1 October 2026 at 10:30 PDT, assigning CVE-2026-97662 to an argument injection in security-agent-mcp-server — the MCP server AWS ships in the awslabs/mcp repository so that AI assistants can run security scans over local source code. NVD published the record the same day at 18:17 UTC, carrying AWS's own CVSS 3.1 score of 8.2 High and a CVSS 4.0 score of 6.9, with CWE-73 and CWE-88. Affected versions are >= 0.1.1 and < 0.2.0. NVD still lists the record as Awaiting Analysis.
The bug is small enough to state in one line: the base_ref argument of the diff-scan tool was placed on a git command line without being checked, so a value beginning with a dash is read by git as an option rather than a revision. The security property it destroys is not small. This server has an explicit workspace-confinement control — and base_ref walks around it.
Where the argument lands
We read scanner.py at the commit immediately before the fix (e7cc8281) and on main today. In the vulnerable state, start_diff_scan builds its command directly:
diff_cmd = ['git', 'diff', base_ref, '--']
and a second call sites the same value inside git archive:
['git', 'archive', '--format=zip', '-o', tmp.name, '--', base_ref]
The archive invocation is already safe — base_ref sits after the -- separator, which is exactly what that separator is for. The diff invocation puts it before the separator, in the position where git is still willing to parse options. That single difference is the whole vulnerability.
Because the two calls look so similar, this is easy to misread as a quoting or shell-metacharacter problem. It is neither. The command is a list, not a shell string; subprocess.run never invokes a shell. There is no command injection here and no path for a semicolon or a backtick. The attacker's entire budget is one git option.
One git option is enough
That budget buys a file write. git diff accepts --output=<file>, which creates or truncates the named path before writing the diff into it. We confirmed the primitive on git 2.53.0 in a throwaway repository: git diff --output=<path> HEAD -- exits 0 and creates the file, with the argument order the vulnerable code produces.
So a base_ref of --output=/home/user/.bashrc writes diff content over that file. There is nothing exotic about the destination: cwd for the subprocess is the scanned workspace, but --output takes an absolute path and ignores it entirely. That is the precise sense in which AWS's bulletin says the issue "bypass[es] the server's workspace-confinement control."
The confinement control itself is real and well-built. _validate_path resolves the requested scan directory with os.path.realpath and rejects anything outside WORKSPACE_ROOT (or the server's cwd), with a comment explaining that the point is to stop an agent scanning ~/.aws or /etc and uploading the contents to S3. The control guards the path parameter. base_ref is a different parameter, and nobody thought of it as a filesystem input, because on its face it is the name of a git revision.
This is the recurring shape of argument injection in agent tooling: a parameter whose type is "harmless identifier" acquires filesystem or network authority purely from its position on a command line. We saw the same mechanism in Bedrock AgentCore's package-install arguments, where a package name reached a sandbox command line, and in the two MCP command injections that landed CVEs in September. A validated path next to an unvalidated ref is not a hardened tool; it is a tool with one door locked.
Who turns the handle
Both CVSS vectors say AV:L and UI:R / UI:P — local attack vector, user interaction required — and AWS describes the actor as "context-dependent." That phrasing is doing real work, and it is worth unpacking rather than skipping.
Nobody reaches this over the network. The caller is the AI assistant that drives the MCP server. The question is therefore who controls the base_ref string the assistant chooses — and in an agentic coding workflow, that string is frequently assembled from untrusted context: a branch name in a pull request, a ref mentioned in an issue, a line in a README, a CI variable. An assistant that reads a repository and then scans a diff against a "base branch" it found in that repository is exactly the pattern this bug monetises. CISA's SSVC entry records exploitation: none and automatable: no, which is accurate and should keep this off anyone's emergency list — but "requires a prompt-injectable agent" is not much of a barrier in 2026, and it is the same bridge we documented when log content reached an LLM debugging agent.
The impact profile reflects this honestly: C:N/I:H/A:H. No confidentiality loss. Integrity and availability loss, because what you get is a truncating write to a file of the attacker's choosing, with content they only loosely control. Overwriting a shell profile, a systemd unit, a CI script or a .git/hooks entry with diff text is destructive first and a code-execution stepping stone second.
The timeline is the other story
The fix is _validate_git_ref, a fifteen-line static method that rejects an empty base_ref and any value whose stripped form starts with -, called at the top of start_diff_scan before the agent-space lookup and before any subprocess runs. Its docstring names the problem exactly: git "would otherwise treat a leading-dash value as an option rather than a revision, which is never intended here."
It arrived in pull request #4508, opened 18 August 2026 and merged as commit 7b69f2e6 on 20 August 2026. It shipped to PyPI in 0.2.0 on 26 August 2026. The CVE was published on 1 October — 36 days after users could already install the fix, and six weeks after the patch was public in the repository.
Read the PR description and nothing announces a vulnerability. It says a bad base_ref "can be misread by git as an option rather than a revision, which leads to confusing behavior and is never intended here," and adds three regression tests asserting that --not-a-ref, -o and whitespace are rejected before any git call, S3 upload or agent-space check happens. The tests are good. The framing is "confusing behavior." Only the October bulletin, crediting independent researcher Mario Guzmán (yud4s) through coordinated disclosure, says the words "bypassing the server's workspace-confinement control."
We have now written this same paragraph about several projects in a month. Obot's advisory said "Patched versions: None" about a bug fixed two months earlier. Here the direction is reversed but the gap is identical: the repository moved in August, the advisory feed caught up in October, and for five weeks the only signal available to a defender was a routine-looking input-validation commit in a monorepo that ships dozens of MCP servers. If your supply-chain monitoring keys on advisories, you waited. If it keys on version drift, you upgraded in August without ever knowing why.
What to do
- Upgrade to 0.2.0 or later; 0.2.1 (8 September 2026) is current. If you pin with
uvx awslabs.security-agent-mcp-server@latest— the installation AWS documents — you are almost certainly already patched and were before the CVE existed. Verify rather than assume: a Docker image tag or a lockfile can hold you at 0.1.x indefinitely. - Check the three 0.1.x releases that shipped after the diff-scan feature. 0.1.1 (17 June) introduced diff scanning; 0.1.2, 0.1.3, 0.1.4 and 0.1.5 all carry the bug through 10 August. Anyone who pinned a version during that window is in the affected range.
- Treat every agent-supplied identifier as command-line input, not as a name. Refs, branches, tags, package names, filter expressions, format strings. The test is positional: can this value be read as an option by the program receiving it? If yes, either validate the leading character or move it after
--. The same file already does the latter correctly forgit archive; the fix forgit diffcould have been['git','diff','--', base_ref]ordering as easily as a validator. - Set
WORKSPACE_ROOTexplicitly and run the server unprivileged. AWS's own workaround guidance — run diff scans only against trusted repositories, least-privileged user, isolated environment — is the right residual control, because it is the only thing that limits what a stray write can reach once an option slips through. There is no configuration toggle that fixes this; AWS states plainly that the only remedy is upgrading. - Do not wait for NVD enrichment. The record is still Awaiting Analysis, so CPE matching is absent and some scanners will not fire on it at all. The AWS bulletin and GHSA-8g28-rj54-p5p2 carry everything needed to make the decision.
Our verification was static and local: we read scanner.py at the pre-fix parent commit and on main, read the merged patch and its tests, and reproduced the git diff --output= write primitive in a scratch repository on git 2.53.0. We did not run the MCP server, and we made no request to any AWS service.
Sources:
- AWS Security Bulletin 2026-121-AWS — "CVE-2026-97662 – Argument injection in AWS security-agent-mcp-server diff scan" (published 1 October 2026 10:30 PDT; impacted >= 0.1.1 and < 0.2.0; resolved in 0.2.0; credits Mario Guzmán / yud4s)
- NVD — CVE-2026-97662 (published 1 October 2026 18:17 UTC; CVSS 3.1 8.2 High, CVSS 4.0 6.9; CWE-73 and CWE-88; SSVC exploitation: none, automatable: no; status Awaiting Analysis)
- GHSA-8g28-rj54-p5p2 — "Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') in awslabs.security-agent-mcp-server" (High, published 1 October 2026; pip package; affected >= 0.1.1, < 0.2.0; patched 0.2.0)
- awslabs/mcp PR #4508 — "fix(security-agent-mcp-server): validate base_ref in start_diff_scan" (opened 18 August 2026, merged 20 August 2026 as commit 7b69f2e6, adding _validate_git_ref plus three regression tests)
- awslabs/mcp — scanner.py on main and at pre-fix commit e7cc8281; source of the diff_cmd, git archive and _validate_git_ref excerpts quoted above
- PyPI — awslabs.security-agent-mcp-server release history (0.1.1 on 17 June 2026 through 0.1.5 on 10 August; 0.2.0 on 26 August 2026; 0.2.1 on 8 September 2026, current)