GIVE YOUR AGENT THE EVIDENCE

Set up in your agent.

One prompt to get started. Paste it into your coding agent to connect cve.new through MCP and bring vulnerability context into your workflow.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

GIVE YOUR AGENT THE EVIDENCE

Investigate in your agent.

Turn this advisory into a focused investigation. Ask your agent to check your project’s versions, explain the evidence, and suggest a compatible fix.

Your agent needs remote MCP support and access to https://cve.new/mcp.

Read the setup guide

GHSA-w8jc-g24h-crhwhigh7.5

GitPython command wrappers omit unsafe protocol and option checks

Check your stack

The advisory, in context

Summary

GitPython has spent 2022-2026 rolling out two reusable guards method-by-method: Git.check_unsafe_protocols(url) (raises UnsafeProtocolError for <transport>:: remote-helper URLs - CVE-2022-24439) and Git.check_unsafe_options(...) (raises UnsafeOptionError for denylisted option spellings - CVE-2023-40267, CVE-2026-42215, CVE-2026-67323). Three wrapper methods that build a git argv from caller-controlled values still have neither:

  1. Git.ls_remote (git/cmd.py:1128-1143) - checks options (:1139-1141) but forwards the URL positional unchecked; no allow_unsafe_protocols parameter exists. The same URL through clone_from raises UnsafeProtocolError.
  2. Repo.merge_base (git/repo/base.py:895-936) - forwards **kwargs/*rev to self.git.merge_base with no guard; guarded sibling iter_commits sits 12 lines up. Its exception handler additionally swallows every non-128 GitCommandError into [], masking the injection.
  3. IndexFile.move (git/index/base.py:1092-1168) - forwards **kwargs to git mv twice (:1147, :1167); guarded sibling remove() is 23 lines above.

Verified unfixed on main @ a54d159 (2026-09-21), post-dating latest release 3.1.62 (2026-09-07) - all three sites re-verified today.

Impact

Site 1 (ls_remote) - RCE conditional on the victim git transport policy. The common 'probe first, then clone' pattern (Git(...).ls_remote(attacker_url)) with an ext::sh -c ... URL executes arbitrary commands when the victim git permits the ext transport: dynamically verified with protocol.ext.allow=always (marker executed). git >= 2.38.1 stock config refuses the transport (fatal: transport 'ext' not allowed, rc 128) - GitPython adds no defense-in-depth and surfaces a bare GitCommandError. Environments on git < 2.38.1 are exposed with stock config - the exact population CVE-2022-24439 was scored against.

Sites 2/3 - latent argument-injection surface: caller kwargs flow 1:1 into the git argv before operands and -- (cmd.py:1850 opt_args + ext_args), and execute_kwargs (incl. shell=True/env - the CVE-2023-40267 Windows mechanism) passes through unguarded. Today git merge-base/git mv expose no exec option, so we claim the missing-guard class (vendor-settled by CVE-2026-67323, which bundled archive/ls_remote/iter_commits/blame in one advisory), not direct RCE.

Dynamic verification (today; GitPython main @ a54d159, git 2.55.0, Linux)

# Call Result
T1 repo.git.ls_remote('ext::/tmp/gp_touch.sh'), stock config git refuses (rc 128); no UnsafeProtocolError from GitPython
T1b same + victim config protocol.ext.allow=always marker created - arbitrary command executed
T2 repo.git.ls_remote('.', upload_pack=...) blocked by cmd.py:1141 (options-side fixed - protocol-side is the residual gap)
T3 repo.merge_base('main','side', upload_pack=...) not blocked; argv git merge-base --upload-pack=... main side; rc 129 silently converted to []
T4 repo.index.move(['f1','f2'], output=...) not blocked; option reaches git mv (rc 129)

Coverage matrix (main @ a54d159)

check_unsafe_protocols: 7 call sites (_clone, archive, set_url, create, fetch, pull, push) - none on ls_remote. check_unsafe_options: 24 call sites incl. ls_remote, iter_commits, IndexFile.remove - absent on merge_base and move.

Precedents (same repo, same guard family)

  • CVE-2022-24439 / GHSA-hcpj-qp55-gfph (critical): check_unsafe_protocols on the clone path - Site 1 is the same ext:: primitive on a sibling API.
  • CVE-2023-40267 / GHSA-pr76-5cm5-w9cj (critical, 'incomplete fix for CVE-2022-24439'): check_unsafe_options - unguarded kwargs to argv treated as RCE (shell=True on Windows).
  • CVE-2026-42215 / GHSA-rpm5-65cw-6hj4 (high, fixed 3.1.47): underscored-spelling bypass - rollout still active in 2026.
  • CVE-2026-67323 / GHSA-956x-8gvw-wg5v (high, 8.4, fixed 3.1.51): added check_unsafe_options to archive/ls_remote/iter_commits/blame; its PoC is literally repo.git.ls_remote('.', upload_pack=...). Establishes that a wrapper missing the check is itself a vulnerability. This report is the residual gap of that rollout: ls_remote got options but not protocols; merge_base/move got neither.

Recommended fix (existing helpers, 1-3 lines per site)

  1. Git.ls_remote: if not allow_unsafe_protocols: self.check_unsafe_protocols(args[0]) + new allow_unsafe_protocols: bool = False (the _clone pattern, base.py:1536-1538).
  2. Repo.merge_base: check_unsafe_options(Git._option_candidates(list(rev), kwargs), unsafe_options=self.unsafe_git_revision_options) (the iter_commits pattern); stop masking non-128 statuses.
  3. IndexFile.move: the remove() pattern with unsafe_git_pathspec_from_file_options.

One advisory, three affected methods, severity anchored on Site 1 - mirroring CVE-2026-67323's bundling.

Affected packages

Start with the recorded fix for your release branch. Check your installed version and test compatibility before upgrading. Fixing this advisory does not rule out other vulnerabilities in the release.

Ecosystem versions

Earlier releases before 3.2.0

Fixed in 3.2.0

115 explicitly affected versions

0.1.7, 0.2.0-beta1, 0.3.0-beta1, 0.3.0-beta2, 0.3.1-beta2, 0.3.2, 0.3.2.1, 0.3.2.RC1, 0.3.3, 0.3.4, 0.3.5, 0.3.6, 0.3.7, 1.0.0, 1.0.1, 1.0.2, 2.0.0, 2.0.1, 2.0.2, 2.0.3, 2.0.4, 2.0.5, 2.0.6, 2.0.7, 2.0.8, 2.0.9, 2.0.9.dev0, 2.0.9.dev1, 2.1.0, 2.1.1, 2.1.10, 2.1.11, 2.1.12, 2.1.13, 2.1.14, 2.1.15, 2.1.2, 2.1.3, 2.1.4, 2.1.5, 2.1.6, 2.1.7, 2.1.8, 2.1.9, 3.0.0, 3.0.1, 3.0.2, 3.0.3, 3.0.4, 3.0.5, 3.0.6, 3.0.7, 3.0.8, 3.0.9, 3.1.0, 3.1.1, 3.1.10, 3.1.11, 3.1.12, 3.1.13, 3.1.14, 3.1.15, 3.1.16, 3.1.17, 3.1.18, 3.1.19, 3.1.2, 3.1.20, 3.1.22, 3.1.23, 3.1.24, 3.1.25, 3.1.26, 3.1.27, 3.1.28, 3.1.29, 3.1.3, 3.1.30, 3.1.31, 3.1.32, 3.1.33, 3.1.34, 3.1.35, 3.1.36, 3.1.37, 3.1.38, 3.1.4, 3.1.40, 3.1.41, 3.1.42, 3.1.43, 3.1.44, 3.1.45, 3.1.46, 3.1.47, 3.1.48, 3.1.49, 3.1.5, 3.1.50, 3.1.51, 3.1.52, 3.1.53, 3.1.54, 3.1.55, 3.1.56, 3.1.57, 3.1.58, 3.1.59, 3.1.6, 3.1.60, 3.1.61, 3.1.62, 3.1.7, 3.1.8, 3.1.9

View affected-version evidence
{
  "ranges": [
    {
      "events": [
        {
          "introduced": "0"
        },
        {
          "fixed": "3.2.0"
        }
      ],
      "type": "ECOSYSTEM"
    }
  ],
  "versions": [
    "0.1.7",
    "0.2.0-beta1",
    "0.3.0-beta1",
    "0.3.0-beta2",
    "0.3.1-beta2",
    "0.3.2",
    "0.3.2.1",
    "0.3.2.RC1",
    "0.3.3",
    "0.3.4",
    "0.3.5",
    "0.3.6",
    "0.3.7",
    "1.0.0",
    "1.0.1",
    "1.0.2",
    "2.0.0",
    "2.0.1",
    "2.0.2",
    "2.0.3",
    "2.0.4",
    "2.0.5",
    "2.0.6",
    "2.0.7",
    "2.0.8",
    "2.0.9",
    "2.0.9.dev0",
    "2.0.9.dev1",
    "2.1.0",
    "2.1.1",
    "2.1.10",
    "2.1.11",
    "2.1.12",
    "2.1.13",
    "2.1.14",
    "2.1.15",
    "2.1.2",
    "2.1.3",
    "2.1.4",
    "2.1.5",
    "2.1.6",
    "2.1.7",
    "2.1.8",
    "2.1.9",
    "3.0.0",
    "3.0.1",
    "3.0.2",
    "3.0.3",
    "3.0.4",
    "3.0.5",
    "3.0.6",
    "3.0.7",
    "3.0.8",
    "3.0.9",
    "3.1.0",
    "3.1.1",
    "3.1.10",
    "3.1.11",
    "3.1.12",
    "3.1.13",
    "3.1.14",
    "3.1.15",
    "3.1.16",
    "3.1.17",
    "3.1.18",
    "3.1.19",
    "3.1.2",
    "3.1.20",
    "3.1.22",
    "3.1.23",
    "3.1.24",
    "3.1.25",
    "3.1.26",
    "3.1.27",
    "3.1.28",
    "3.1.29",
    "3.1.3",
    "3.1.30",
    "3.1.31",
    "3.1.32",
    "3.1.33",
    "3.1.34",
    "3.1.35",
    "3.1.36",
    "3.1.37",
    "3.1.38",
    "3.1.4",
    "3.1.40",
    "3.1.41",
    "3.1.42",
    "3.1.43",
    "3.1.44",
    "3.1.45",
    "3.1.46",
    "3.1.47",
    "3.1.48",
    "3.1.49",
    "3.1.5",
    "3.1.50",
    "3.1.51",
    "3.1.52",
    "3.1.53",
    "3.1.54",
    "3.1.55",
    "3.1.56",
    "3.1.57",
    "3.1.58",
    "3.1.59",
    "3.1.6",
    "3.1.60",
    "3.1.61",
    "3.1.62",
    "3.1.7",
    "3.1.8",
    "3.1.9"
  ]
}

References