# CVE-2026-6980 — Divyanshu-hash/GitPilot-MCP repo_path injection

| | |
|---|---|
| **Disclosed** | 2026-04-25 ([RedPacket Security](https://www.redpacketsecurity.com/cve-alert-cve-2026-6980-divyanshu-hash-gitpilot-mcp/)) |
| **CVSS v3** | 7.3 |
| **Affected** | `Divyanshu-hash/GitPilot-MCP` (no version pin — project does not version) |
| **Patch status** | **Vendor unresponsive** — no upstream fix |
| **agent-airlock preset** | `gitpilot_mcp_cve_2026_6980_defaults()` (v0.5.7+) |

## What broke

GitPilot-MCP's `main.py` received a `repo_path` argument that flowed
unsanitised into OS command execution. The public PoC used a
backtick:

```python
# vulnerable handler — repo_path becomes part of a shell-evaluated string
repo_path = "/tmp/repo`whoami`"
# ...
os.system(f"cd {repo_path} && git status")
# whoami runs before git ever sees the path
```

Three handler names cross the boundary: `repo_path`, `run_git_command`,
`exec_in_repo`.

## Why "preset, not version pin"

Vendor unresponsive; project does not version. The agent-airlock
preset matches purely on **tool name regex** (`r"^(repo_path|run_git_command|exec_in_repo)$"`),
not on package or upstream version. There is no future "fixed-in"
gate — the preset stays evergreen until the upstream project either
ships a fix or the tool names rename.

## How agent-airlock blocks it

```python
from pathlib import Path
from agent_airlock import GitPilotRepoPathInjection
from agent_airlock.policy_presets import gitpilot_mcp_cve_2026_6980_defaults

cfg = gitpilot_mcp_cve_2026_6980_defaults(
    safe_repo_roots=(Path("/var/repos"),),
)

try:
    cfg["check"]("repo_path", {"repo_path": "/tmp/repo`whoami`"})
except GitPilotRepoPathInjection as e:
    print(f"blocked {e.handler_name}: {e.reason}")
    # blocked repo_path: shell-metacharacter present (shlex round-trip failed)
```

The check applies four rules in order:

1. Tool name must match `^(repo_path|run_git_command|exec_in_repo)$`
   — otherwise the preset is a no-op (other handlers are unaffected).
2. `shlex.quote(repo_path) == repo_path` — rejects every shell
   metacharacter without enumerating them.
3. `repo_path` must be absolute. Relative paths (`./`, `../../`)
   raise.
4. If `safe_repo_roots` is non-empty, the resolved path must remain
   under one of them. Path traversal that escapes the safe root
   is caught at this step.

## Direct primitive usage

For handlers outside the GitPilot name set that still want
shlex-round-trip + safe-root validation, lift the logic into your
own preset rather than calling internal helpers — the preset
contract is the public surface.

## Primary sources

- [RedPacket Security CVE alert (2026-04-25)](https://www.redpacketsecurity.com/cve-alert-cve-2026-6980-divyanshu-hash-gitpilot-mcp/)
- [vulnerability.circl.lu](https://vulnerability.circl.lu/vuln/CVE-2026-6980)
- Fixture: `tests/cves/fixtures/cve_2026_6980_gitpilot.json`
