FlawOpen

flawopen.com/Teardowns/cve-2026-97662-aws-security-agent-mcp-git-argument-injection

● CVE-2026-97662 · CVSS v4 6.9 · Medium
FlawOpen Security Research

CVE-2026-97662: AWS Security Agent MCP Git Argument Injection

CVE-2026-97662 let a caller-influenced Git revision in AWS Security Agent MCP diff scans be parsed as a command option, bypassing workspace confinement and enabling host file writes. Upgrade to 0.2.0 or later.[1], [2]

CVSS vectorCVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N
💡 Plain English Explainer (ELI5)

A clerk is told to find a folder by its label, but accepts a label that starts with a control instruction. The filing system follows the instruction instead of looking for a folder. Here, a Git revision beginning with a dash could be interpreted as a Git option; rejecting empty and option-like refs before starting Git closes that input boundary. This is Git option parsing, not arbitrary shell-command execution.[2]

Core Concepts & Subsystem Terms

MCP server
A local process that exposes tools to an AI application through the Model Context Protocol. Tool arguments cross into the server as untrusted input.
Argument injection (CWE-88)
Untrusted input is interpreted as an option or delimiter by a command-line program, even when the caller uses an argument array instead of a shell string.[2], [4]
Git revision
A name or expression identifying a commit or tree, such as main or HEAD~1. A value beginning with - may be parsed as an option unless validated or passed with an option terminator supported by that command.[2], [3]
Workspace confinement
A boundary intended to keep a tool's file operations within the selected project directory. It is incomplete if an input can change the meaning of a subprocess command.[1], [3]

Framework mappings

Root Cause Analysis

AWS describes a context-dependent argument-injection flaw in the diff scan operation: a crafted reference supplied to the scan could be treated as a command-line option rather than a Git revision. The upstream PR confirms that start_diff_scan passed base_ref to git diff <base_ref> -- and git archive; empty or leading-dash values could therefore be misinterpreted. This is not shell metacharacter injection: an argument list can prevent shell parsing while still allowing the target program to parse an argument as an option. The advisory says the resulting operation could create, overwrite, or truncate files outside the intended workspace, with writes limited by the server process's OS permissions. The merged fix validates base_ref before Git, rejecting blank values and values whose trimmed form starts with -; tests assert no subprocess or upload occurs for rejected input. The advisory identifies versions >=0.1.1 and <0.2.0 as affected and 0.2.0 as fixed. CVE record scoring is CVSS 4.0 6.9 Medium; the GitHub advisory also lists CVSS 3.1 8.2 High. No exploit payload is reproduced here.[1], [2], [3], [4]

Step-by-Step Attack Flow

Step 1

An agent invokes a diff-scan tool

The local MCP server receives a caller-influenced base_ref. The risk depends on a workflow that allows an untrusted repository, content source, or user to influence that value; a prompt injection alone does not automatically reach this code path.[2]

Step 2

The value reaches Git as a positional argument

The affected code passes the reference to Git commands used for diff and archive operations. A leading dash can make Git parse the value as an option rather than a revision, even though Python invokes the process without a shell.[2], [3]

Step 3

Git's option behavior escapes the workspace assumption

A crafted option can redirect file output outside the selected workspace. The documented impact is create, overwrite, or truncate files the server process may write; actual reach depends on host permissions and the affected workflow.[1], [3]

Step 4

Reject unsafe refs before side effects

Version 0.2.0 validates base_ref before Git, S3 upload, or agent-space checks. Upgrade affected installations and keep the MCP process least-privileged and isolated.[1], [3], [6]

Boundary trace: revision text versus Git options

Conceptual data-flow trace, not a command sequence or exploit reproduction.

Two parsers, two separate boundaries

Shell parsing
An argument array keeps the shell from interpreting the value as shell syntax, closing that boundary.[3]
Git option parsing
Git still parses its own command-line grammar. An argument array does not stop a caller-controlled ref from being interpreted as an option; validate it before invoking Git.[2], [3]
1

A caller influences the revision

The diff-scan tool receives base_ref from a workflow. The risk exists only when an untrusted source can influence that value.[2], [3]

2

An argument array still reaches Git's parser

Python can pass each value separately and avoid shell parsing, while Git still interprets its own command-line options.[3], [2]

3

The workspace assumption can fail

If Git treats the value as an option, file output can escape the intended workspace, bounded by the MCP process's operating-system permissions.[1], [2]

4

Validation closes the input boundary

Version 0.2.0 rejects empty and option-like refs before Git or later side effects. Caller input can no longer select Git option behavior; valid refs still select the requested revision.[3], [6]

Source Code: Flaw vs. Secure Implementation

This is a focused representation of the upstream call boundary, not a complete copy of the scanner. The merged upstream change validates the reference before Git and tests blank and leading-dash values, including that rejected input causes no subprocess or upload. An argument array avoids shell parsing but does not stop Git from parsing an option. The validation should remain adjacent to every use of base_ref in Git commands. This excerpt shows only the git diff call; the same validation must guard every use of base_ref, including git archive.[3]

Source excerpt: src/security-agent-mcp-server/awslabs/security_agent_mcp_server/scanner.py — start_diff_scan and _validate_git_ref

UNPATCHED FLAW
def start_diff_scan(path: str, base_ref: str):
    # The option-like ref reaches Git before validation.
    return subprocess.run(
        ["git", "diff", base_ref, "--"],
        cwd=path,
        check=False,
    )
HARDENED SECURE PATCH
def _validate_git_ref(base_ref: str) -> None:
    if not base_ref or not base_ref.strip():
        raise ValueError("base_ref must not be empty.")
    if base_ref.strip().startswith("-"):
        raise ValueError("base_ref must not start with '-'.")

def start_diff_scan(path: str, base_ref: str):
    _validate_git_ref(base_ref)  # Reject before subprocesses or uploads.
    return subprocess.run(
        ["git", "diff", base_ref, "--"],
        cwd=path,
        check=False,
    )

Engineering & System Hardening Checklist

Sources