modelcontextprotocol/servers · error · BadName

Invalid target: '{target}' - cannot start with '-'

Error message

Invalid target: '{target}' - cannot start with '-'

What it means

git_diff() rejects a target that starts with '-' as defense-in-depth against flag injection into `git diff` (e.g. a target like --upload-pack=...). The check runs before repo.rev_parse(target), so it blocks the value regardless of whether such a ref exists. Raises gitdb BadName, which propagates raw to the client because the git server runs with raise_exceptions=True and call_tool does not wrap it.

Source

Thrown at src/git/src/mcp_server_git/server.py:124

    CHECKOUT = "git_checkout"
    SHOW = "git_show"

    BRANCH = "git_branch"

def git_status(repo: git.Repo) -> str:
    return repo.git.status()

def git_diff_unstaged(repo: git.Repo, context_lines: int = DEFAULT_CONTEXT_LINES) -> str:
    return repo.git.diff(f"--unified={context_lines}")

def git_diff_staged(repo: git.Repo, context_lines: int = DEFAULT_CONTEXT_LINES) -> str:
    return repo.git.diff(f"--unified={context_lines}", "--cached")

def git_diff(repo: git.Repo, target: str, context_lines: int = DEFAULT_CONTEXT_LINES) -> str:
    # Defense in depth: reject targets starting with '-' to prevent flag injection,
    # even if a malicious ref with that name exists (e.g. via filesystem manipulation)
    if target.startswith("-"):
        raise BadName(f"Invalid target: '{target}' - cannot start with '-'")
    repo.rev_parse(target)  # Validates target is a real git ref, throws BadName if not
    return repo.git.diff(f"--unified={context_lines}", target)

def git_commit(repo: git.Repo, message: str) -> str:
    commit = repo.index.commit(message)
    return f"Changes committed successfully with hash {commit.hexsha}"

def git_add(repo: git.Repo, files: list[str]) -> str:
    if files == ["."]:
        repo.git.add(".")
    else:
        # Defense in depth: validate each path resolves within the repository
        # working tree to prevent path traversal (e.g. '../../etc/passwd' or an
        # absolute path) from staging files outside repository boundaries.
        repo_root = Path(repo.working_dir).resolve()
        for f in files:
            try:
                resolved = (repo_root / f).resolve()

View on GitHub (pinned to 76d64c822f)

Solutions

  1. Reject or strip a leading '-' from target before calling.
  2. Use a valid ref (branch, tag, or commit SHA) that does not start with '-'.
  3. If the value legitimately could be an option, pass it through the proper option parameter instead of target.

Example fix

# before
git_diff(repo, target='-eMalicious')  # -> BadName

# after
def safe_target(t: str) -> str:
    if t.startswith('-'):
        raise ValueError('target must not start with -')
    return t
git_diff(repo, safe_target(target))
Defensive patterns

Strategy: validation

Validate before calling

def safe_ref(target: str) -> str:
    if not target or target.startswith('-'):
        raise ValueError('target must be a non-empty ref not starting with -')
    return target

Try / catch

from gitdb.exc import BadName
try:
    git_diff(repo, target)
except BadName as e:
    if 'cannot start with' in str(e):
        # reject/normalize input
    raise

Prevention

When it happens

Trigger: Calling git_diff with a target string beginning with '-'; malicious or malformed ref input.

Common situations: Adversarial tool input; copy-paste artifacts; a UI that lets users pass arbitrary ref text.

Related errors


AI-assisted analysis of modelcontextprotocol/servers@76d64c822f (2026-08-12). Data as JSON: /api/errors/5f60fc1a1e3890eb. Report an issue: GitHub.