GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration
brew-aider-cve-2026-76220
Vulnerability from osv_homebrew
Published
2026-08-20 08:39
Modified
2026-09-11 15:20
Summary
GitPython: Unsafe git option guard bypass via split_single_char_options=False short-option token smuggling enables command execution
Details

Summary

The 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<value> argv token that git parses as --upload-pack=<cmd>, 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.

Root Cause

_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 <cmd>;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 <cmd>;git-upload-pack (cmd.py:1631). git clusters value-less short flags then parses -u<cmd> = --upload-pack=<cmd> → command execution. The hardened guard WOULD block the joined token if it saw it — the flaw is it never receives it.

Impact

Arbitrary 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).

Proof of Concept

from git import Repo
Repo.clone_from(src, dst,
    n="utouch /tmp/ACE;git-upload-pack",
    split_single_char_options=False)   # /tmp/ACE created -> ACE

Attack Chain

  1. Entry: app forwards user kwargs to Repo.clone_from(url, path, **kwargs): {split_single_char_options: False, n: 'utouch /tmp/ACE;git-upload-pack'}.
  2. 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.
  3. Sink: transform_kwarg emits joined token (cmd.py:1631). argv (observed): ['git','clone','-v','-nutouch /tmp/ACE;git-upload-pack','--','<src>','<dst>'].
  4. Impact: git clusters -n + -u<cmd> → runs upload-pack command → ACE.

Bypass Evidence

Independently 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'].

Affected Versions

GitPython <= 3.1.57 (code present verbatim on the latest release tag).

Suggested Fix

Make _option_candidates emit value-derived candidates regardless of split_single_char_options (i.e. also for the joined -n<value> form), OR run check_unsafe_options over the fully-transformed argv rather than the reconstructed name-only candidate list.


Reported by zx (Jace) — GitHub: @manus-use


{
  "affected": [
    {
      "ecosystem_specific": {
        "fix": null,
        "range_state": "affected",
        "resource": "gitpython",
        "resource_purl": "pkg:pypi/gitpython@3.1.46",
        "upstream_fixed_in": "3.1.58"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "aider",
        "purl": "pkg:brew/aider"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/gitpython@3.1.46",
        "name": "gitpython",
        "resource": "gitpython",
        "strategy": "registry",
        "subject_version": "3.1.46"
      }
    ]
  },
  "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\u0027s 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 `[\u0027-n\u0027]` (not on the denylist), so the guard passes. But `transform_kwarg(\u0027n\u0027, 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` \u2192 command execution. The hardened guard WOULD block the joined token if it saw it \u2014 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: \u0027utouch /tmp/ACE;git-upload-pack\u0027}`.\n2. Check: `check_unsafe_options(_option_candidates([], kwargs), unsafe_git_clone_options)`. Guard: denylist includes `--upload-pack`/`-u`. Bypass proof: `_option_candidates` yields only `[\u0027-n\u0027]` (value token skipped because `split=False`); guard never sees `-u`.\n3. Sink: `transform_kwarg` emits joined token (cmd.py:1631). argv (observed): `[\u0027git\u0027,\u0027clone\u0027,\u0027-v\u0027,\u0027-nutouch /tmp/ACE;git-upload-pack\u0027,\u0027--\u0027,\u0027\u003csrc\u003e\u0027,\u0027\u003cdst\u003e\u0027]`.\n4. Impact: git clusters `-n` + `-u\u003ccmd\u003e` \u2192 runs upload-pack command \u2192 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=\u0027--upload-pack=\u2026\u0027` (split default True) \u2192 `UnsafeOptionError: --upload-pack is not allowed`. Fix-commit read: `e8d0fbf7` extends candidates only under `if len(key)==1 and split_single_char_options:` \u2014 split=False skips value emission. Also confirmed the earlier clustering-parse fix (commit `56806080`) does not cover this because the guard only ever receives `[\u0027-n\u0027]`.\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)** \u2014 GitHub: @manus-use",
  "id": "BREW-aider-CVE-2026-76220",
  "modified": "2026-09-11T15:20:33Z",
  "published": "2026-08-20T08:39:09Z",
  "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"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "GitPython: Unsafe git option guard bypass via split_single_char_options=False short-option token smuggling enables command execution",
  "upstream": [
    "GHSA-wvpp-8hx9-p66j",
    "CVE-2026-76220",
    "PYSEC-2026-3843"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…