{"id":"PYSEC-2026-3843","summary":"GitPython: Unsafe git option guard bypass via split_single_char_options=False short-option token smuggling enables command execution","details":"## Summary\nThe `check_unsafe_options` guard can be bypassed on every guarded method (clone/clone_from, fetch/pull/push, ls_remote, iter_commits, blame, archive) by combining a single-character kwarg with `split_single_char_options=False`. The guard's candidate list omits the smuggled option, but `transform_kwarg` emits a JOINED `-n\u003cvalue\u003e` argv token that git parses as `--upload-pack=\u003ccmd\u003e`, yielding arbitrary command execution at the default `allow_unsafe_options=False`. This is an incomplete-fix bypass of commit `e8d0fbf7` (the fix for GHSA-r9mr-m37c-5fr3), which only emits value-derived candidates when `split_single_char_options` is True.\n\n## Root Cause\n`_option_candidates` derives value-token candidates only under `if len(key)==1 and split_single_char_options:` (cmd.py:1048, added by `e8d0fbf7`). With `split_single_char_options=False`, `_option_candidates([], {\"n\":\"utouch \u003ccmd\u003e;git-upload-pack\"})` returns only `['-n']` (not on the denylist), so the guard passes. But `transform_kwarg('n', value, split_single_char_options=False)` emits the JOINED token `-nutouch \u003ccmd\u003e;git-upload-pack` (cmd.py:1631). git clusters value-less short flags then parses `-u\u003ccmd\u003e` = `--upload-pack=\u003ccmd\u003e` → command execution. The hardened guard WOULD block the joined token if it saw it — the flaw is it never receives it.\n\n## Impact\nArbitrary OS command execution as the host process (via `--upload-pack`) at default `allow_unsafe_options=False`, affecting all guarded methods that forward kwargs. Precondition: the app forwards a user-controlled kwargs dict containing `split_single_char_options=False` plus a single-char key (same user-dict-forwarding model GHSA-r9mr-m37c-5fr3 accepts).\n\n## Proof of Concept\n```python\nfrom git import Repo\nRepo.clone_from(src, dst,\n    n=\"utouch /tmp/ACE;git-upload-pack\",\n    split_single_char_options=False)   # /tmp/ACE created -\u003e ACE\n```\n\n## Attack Chain\n1. Entry: app forwards user kwargs to `Repo.clone_from(url, path, **kwargs)`: `{split_single_char_options: False, n: 'utouch /tmp/ACE;git-upload-pack'}`.\n2. Check: `check_unsafe_options(_option_candidates([], kwargs), unsafe_git_clone_options)`. Guard: denylist includes `--upload-pack`/`-u`. Bypass proof: `_option_candidates` yields only `['-n']` (value token skipped because `split=False`); guard never sees `-u`.\n3. Sink: `transform_kwarg` emits joined token (cmd.py:1631). argv (observed): `['git','clone','-v','-nutouch /tmp/ACE;git-upload-pack','--','\u003csrc\u003e','\u003cdst\u003e']`.\n4. Impact: git clusters `-n` + `-u\u003ccmd\u003e` → runs upload-pack command → ACE.\n\n## Bypass Evidence\nIndependently reproduced (gate harness, default `allow_unsafe_options=False`): the `split=False` payload created the marker `VH05_GATE_ACE` (ACE); the clone returned normally (guard bypassed). Control: `n='--upload-pack=…'` (split default True) → `UnsafeOptionError: --upload-pack is not allowed`. Fix-commit read: `e8d0fbf7` extends candidates only under `if len(key)==1 and split_single_char_options:` — split=False skips value emission. Also confirmed the earlier clustering-parse fix (commit `56806080`) does not cover this because the guard only ever receives `['-n']`.\n\n## Affected Versions\n`GitPython \u003c= 3.1.57` (code present verbatim on the latest release tag).\n\n## Suggested Fix\nMake `_option_candidates` emit value-derived candidates regardless of `split_single_char_options` (i.e. also for the joined `-n\u003cvalue\u003e` form), OR run `check_unsafe_options` over the fully-transformed argv rather than the reconstructed name-only candidate list.\n\n---\nReported by **zx (Jace)** — GitHub: @manus-use","aliases":["CVE-2026-76220","GHSA-wvpp-8hx9-p66j"],"modified":"2026-09-10T12:15:03.997640280Z","published":"2026-09-10T09:44:51.423124Z","references":[{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-wvpp-8hx9-p66j"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-76220"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/pull/2204"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/commit/96a888f4d782cb2f80452148e48e60ce4af6d541"},{"type":"PACKAGE","url":"https://github.com/gitpython-developers/GitPython"},{"type":"WEB","url":"https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58"},{"type":"WEB","url":"https://www.vulncheck.com/advisories/gitpython-before-command-execution-via-split-single-char-options"},{"type":"PACKAGE","url":"https://pypi.org/project/gitpython"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-wvpp-8hx9-p66j"}],"affected":[{"package":{"name":"gitpython","ecosystem":"PyPI","purl":"pkg:pypi/gitpython"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"3.1.58"}]}],"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.6","3.1.7","3.1.8","3.1.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/gitpython/PYSEC-2026-3843.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"}]}