brew-cobo-cli-cve-2026-78678
Vulnerability from osv_homebrew
Summary
Repo.blame() / Repo.blame_incremental() guard forwarded revision options against unsafe_git_revision_options, but that denylist only contains the file-WRITE options --output/-o. git blame also honors --contents <file> and -S <file>, which cause the file's lines to be echoed into the blame result — an arbitrary file READ. Neither option is in the denylist, so a caller-influenced revision value of --contents=<path> passes the guard and leaks file contents. This is a distinct sink-option and impact class (READ) from GHSA-956x-8gvw-wg5v (which addressed the blame --output WRITE), directly analogous to GHSA-539m-9xh6-q6rr (archive READ gap accepted separately from the archive write/exec advisory).
Root Cause
unsafe_git_revision_options = ["--output","-o"] (git/repo/base.py:188). The rev string is passed to _option_candidates([rev], kwargs) and placed BEFORE the -- separator (base.py:841). The canonical name of --contents=... is contents, which is not on the denylist, so no UnsafeOptionError is raised. The trailing -- protects only the pathspec, not the option before the revision.
Impact
Arbitrary local file read at the privileges of the host process; the file's line contents appear in the blame result returned to the caller. Pure VALUE control (the caller forwards a user-influenced revision string). Default allow_unsafe_options=False.
Proof of Concept
result = repo.blame("--contents=/etc/passwd", "a.txt")
# result rows carry the victim file's line text
Attack Chain
- Entry: app calls
repo.blame(rev, file)with attackerrev="--contents=/etc/passwd"(or kwargcontents="/etc/passwd", or-S). - Check:
Git.check_unsafe_options(_option_candidates([rev,...], kwargs), unsafe_git_revision_options)@ base.py:841. Guard: denylist =["--output","-o"]only. Bypass proof: canonical namecontents∉ denylist → no error. - Sink:
self.git.blame(rev, "--", file, p=True, ...). argv (observed):['git','blame','-p','--contents=<secret>','HEAD','--','a.txt']. - Impact: blame result rows carry the victim file's line text.
Bypass Evidence
Independently reproduced (independent test harness, default allow_unsafe_options=False): blame('--contents=<secret>','a.txt') → guard PASSED; result rows = ['GATE_SECRET_LINE_A','GATE_SECRET_LINE_B']. Control: blame('--output=…') still BLOCKED (guard active on this path). -S kwarg argv also reaches git unguarded.
Affected Versions
GitPython <= 3.1.58 (denylist present verbatim on the latest release tag).
Suggested Fix
Prefer an allowlist of blame options; at minimum add --contents/-S (and any other path-taking blame options) to unsafe_git_revision_options, and make the membership rule "the option takes a filesystem path" rather than "the option writes output".
Reported by zx (Jace) — GitHub: @manus-use
{
"affected": [
{
"ecosystem_specific": {
"fix": null,
"range_state": "affected",
"resource": "gitpython",
"resource_purl": "pkg:pypi/gitpython@3.1.50",
"upstream_fixed_in": "3.1.59"
},
"package": {
"ecosystem": "Homebrew",
"name": "cobo-cli",
"purl": "pkg:brew/cobo-cli"
},
"ranges": [
{
"events": [
{
"introduced": "0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"database_specific": {
"confidence": "high",
"source": "matched",
"strategy": "registry",
"upstream_evidence": [
{
"ecosystem": "PyPI",
"key": "pkg:pypi/gitpython@3.1.50",
"name": "gitpython",
"resource": "gitpython",
"strategy": "registry",
"subject_version": "3.1.50"
}
]
},
"details": "## Summary\n`Repo.blame()` / `Repo.blame_incremental()` guard forwarded revision options against `unsafe_git_revision_options`, but that denylist only contains the file-WRITE options `--output`/`-o`. `git blame` also honors `--contents \u003cfile\u003e` and `-S \u003cfile\u003e`, which cause the file\u0027s lines to be echoed into the blame result \u2014 an arbitrary file READ. Neither option is in the denylist, so a caller-influenced revision value of `--contents=\u003cpath\u003e` passes the guard and leaks file contents. This is a distinct sink-option and impact class (READ) from GHSA-956x-8gvw-wg5v (which addressed the blame `--output` WRITE), directly analogous to GHSA-539m-9xh6-q6rr (archive READ gap accepted separately from the archive write/exec advisory).\n\n## Root Cause\n`unsafe_git_revision_options = [\"--output\",\"-o\"]` (`git/repo/base.py:188`). The `rev` string is passed to `_option_candidates([rev], kwargs)` and placed BEFORE the `--` separator (base.py:841). The canonical name of `--contents=...` is `contents`, which is not on the denylist, so no `UnsafeOptionError` is raised. The trailing `--` protects only the pathspec, not the option before the revision.\n\n## Impact\nArbitrary local file read at the privileges of the host process; the file\u0027s line contents appear in the blame result returned to the caller. Pure VALUE control (the caller forwards a user-influenced revision string). Default `allow_unsafe_options=False`.\n\n## Proof of Concept\n```python\nresult = repo.blame(\"--contents=/etc/passwd\", \"a.txt\")\n# result rows carry the victim file\u0027s line text\n```\n\n## Attack Chain\n1. Entry: app calls `repo.blame(rev, file)` with attacker `rev=\"--contents=/etc/passwd\"` (or kwarg `contents=\"/etc/passwd\"`, or `-S`).\n2. Check: `Git.check_unsafe_options(_option_candidates([rev,...], kwargs), unsafe_git_revision_options)` @ base.py:841. Guard: denylist = `[\"--output\",\"-o\"]` only. Bypass proof: canonical name `contents` \u2209 denylist \u2192 no error.\n3. Sink: `self.git.blame(rev, \"--\", file, p=True, ...)`. argv (observed): `[\u0027git\u0027,\u0027blame\u0027,\u0027-p\u0027,\u0027--contents=\u003csecret\u003e\u0027,\u0027HEAD\u0027,\u0027--\u0027,\u0027a.txt\u0027]`.\n4. Impact: blame result rows carry the victim file\u0027s line text.\n\n## Bypass Evidence\nIndependently reproduced (independent test harness, default `allow_unsafe_options=False`): `blame(\u0027--contents=\u003csecret\u003e\u0027,\u0027a.txt\u0027)` \u2192 guard PASSED; result rows = `[\u0027GATE_SECRET_LINE_A\u0027,\u0027GATE_SECRET_LINE_B\u0027]`. Control: `blame(\u0027--output=\u2026\u0027)` still BLOCKED (guard active on this path). `-S` kwarg argv also reaches git unguarded.\n\n## Affected Versions\n`GitPython \u003c= 3.1.58` (denylist present verbatim on the latest release tag).\n\n## Suggested Fix\nPrefer an allowlist of blame options; at minimum add `--contents`/`-S` (and any other path-taking blame options) to `unsafe_git_revision_options`, and make the membership rule \"the option takes a filesystem path\" rather than \"the option writes output\".\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
"id": "BREW-cobo-cli-CVE-2026-78678",
"modified": "2026-09-09T23:48:13Z",
"published": "2026-09-04T08:51:28Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-5xxx-qhh7-9287"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-78678"
},
{
"type": "PACKAGE",
"url": "https://github.com/gitpython-developers/GitPython"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3788.yaml"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/gitpython-before-arbitrary-file-read-via-repo-blame"
}
],
"schema_version": "1.7.3",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "GitPython: Incomplete unsafe_git_revision_options denylist omits --contents/-S, enabling arbitrary file read via Repo.blame()",
"upstream": [
"PYSEC-2026-3788",
"CVE-2026-78678",
"GHSA-5xxx-qhh7-9287"
]
}
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.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
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.