BREW-NBDIME-CVE-2026-110857 (GHSA-W8JC-G24H-CRHW)

Vulnerability from osv_homebrew – Published: 2026-10-28 09:23 – Updated: 2026-10-28 09:23 – Source website
VLAI
Summary
GitPython command wrappers omit unsafe protocol and option checks
Details

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": [
    {
      "ecosystem_specific": {
        "fix": "bump",
        "range_state": "fixed",
        "resource": "gitpython",
        "resource_purl": "pkg:pypi/gitpython@3.2.1",
        "upstream_fixed_in": "3.2.0"
      },
      "package": {
        "ecosystem": "Homebrew",
        "name": "nbdime",
        "purl": "pkg:brew/nbdime"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.1.0"
            },
            {
              "fixed": "4.0.4_9"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "database_specific": {
    "confidence": "high",
    "source": "matched",
    "strategy": "registry",
    "upstream_evidence": [
      {
        "ecosystem": "PyPI",
        "key": "pkg:pypi/gitpython@3.2.1",
        "name": "gitpython",
        "resource": "gitpython",
        "strategy": "registry",
        "subject_version": "3.2.1"
      }
    ]
  },
  "details": "### Summary\n\nGitPython has spent 2022-2026 rolling out two reusable guards method-by-method: `Git.check_unsafe_protocols(url)` (raises `UnsafeProtocolError` for `\u003ctransport\u003e::` 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**:\n\n1. **`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`.\n2. **`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.\n3. **`IndexFile.move`** (git/index/base.py:1092-1168) - forwards `**kwargs` to `git mv` twice (:1147, :1167); guarded sibling `remove()` is 23 lines above.\n\nVerified unfixed on `main @ a54d159` (2026-09-21), post-dating latest release 3.1.62 (2026-09-07) - all three sites re-verified today.\n\n### Impact\n\n**Site 1 (`ls_remote`)** - RCE conditional on the victim git transport policy. The common \u0027probe first, then clone\u0027 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 \u003e= 2.38.1 stock config refuses the transport (`fatal: transport \u0027ext\u0027 not allowed`, rc 128) - GitPython adds no defense-in-depth and surfaces a bare `GitCommandError`. Environments on git \u003c 2.38.1 are exposed with stock config - the exact population CVE-2022-24439 was scored against.\n\n**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.\n\n### Dynamic verification (today; GitPython main @ a54d159, git 2.55.0, Linux)\n\n| # | Call | Result |\n|---|---|---|\n| T1 | `repo.git.ls_remote(\u0027ext::/tmp/gp_touch.sh\u0027)`, stock config | git refuses (rc 128); **no `UnsafeProtocolError` from GitPython** |\n| T1b | same + victim config `protocol.ext.allow=always` | **marker created - arbitrary command executed** |\n| T2 | `repo.git.ls_remote(\u0027.\u0027, upload_pack=...)` | blocked by cmd.py:1141 (options-side fixed - protocol-side is the residual gap) |\n| T3 | `repo.merge_base(\u0027main\u0027,\u0027side\u0027, upload_pack=...)` | **not blocked**; argv `git merge-base --upload-pack=... main side`; rc 129 silently converted to `[]` |\n| T4 | `repo.index.move([\u0027f1\u0027,\u0027f2\u0027], output=...)` | **not blocked**; option reaches `git mv` (rc 129) |\n\n### Coverage matrix (main @ a54d159)\n\n`check_unsafe_protocols`: 7 call sites (`_clone`, `archive`, `set_url`, `create`, `fetch`, `pull`, `push`) - none on `ls_remote`.\n`check_unsafe_options`: 24 call sites incl. `ls_remote`, `iter_commits`, `IndexFile.remove` - absent on `merge_base` and `move`.\n\n### Precedents (same repo, same guard family)\n\n- **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.\n- **CVE-2023-40267 / GHSA-pr76-5cm5-w9cj** (critical, \u0027incomplete fix for CVE-2022-24439\u0027): `check_unsafe_options` - unguarded kwargs to argv treated as RCE (`shell=True` on Windows).\n- **CVE-2026-42215 / GHSA-rpm5-65cw-6hj4** (high, fixed 3.1.47): underscored-spelling bypass - rollout still active in 2026.\n- **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(\u0027.\u0027, 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.\n\n### Recommended fix (existing helpers, 1-3 lines per site)\n\n1. `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).\n2. `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.\n3. `IndexFile.move`: the `remove()` pattern with `unsafe_git_pathspec_from_file_options`.\n\nOne advisory, three affected methods, severity anchored on Site 1 - mirroring CVE-2026-67323\u0027s bundling.",
  "id": "BREW-nbdime-CVE-2026-110857",
  "modified": "2026-10-28T09:23:24Z",
  "published": "2026-10-28T09:23:24Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-w8jc-g24h-crhw"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/pull/2251"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/commit/9ad28cc4c7176d22cd6181d69805058a719dd03b"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gitpython-developers/GitPython"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.2.0"
    }
  ],
  "schema_version": "1.7.3",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "GitPython command wrappers omit unsafe protocol and option checks",
  "upstream": [
    "GHSA-w8jc-g24h-crhw",
    "CVE-2026-110857"
  ]
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.

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.
Vulnerability timeline

Sightings and recorded dates, grouped by day · UTC

JSON
Events included in the timeline

Loading timeline…


    Loading…

    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.