GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-3WXW-XV34-2FRG

Vulnerability from github – Published: 2026-09-08 19:42 – Updated: 2026-09-08 19:42
VLAI
Summary
GitPython: TagReference.create positional reference bypasses kwargs-only --file guard, enabling arbitrary file read (incomplete fix of 3af0c251)
Details

Summary

TagReference.create() forwards a caller-influenced positional reference value into git tag without it ever being inspected by the unsafe-option guard, allowing an arbitrary file read (the file's contents are returned in-band as the annotated tag message). This is an incomplete-fix bypass of commit 3af0c251 (the fix for GHSA-3f7w-8rr8-f37f's tag instance).

Root Cause

The fix 3af0c251 added unsafe_git_tag_options = ["--file","-F"] and a guard call, but the guard is Git.check_unsafe_options(options=Git._option_candidates([], kwargs), unsafe_options=...) at git/refs/tag.py:139 — it passes an EMPTY args list and inspects kwargs only. The dangerous values path and reference are POSITIONALS (args = (path, reference), tag.py:156), placed before any --. A user-influenced reference="--file=<path>" therefore reaches git tag as the exact --file option the fix intended to block, creating an annotated tag whose message is the file's contents.

Impact

Arbitrary local file read at the privileges of the host process; contents returned in-band via tagref.tag.message. Requires the embedding application to forward a caller-influenced reference value into TagReference.create() (pure VALUE control — the CVE-2026-42215 threat model). Default allow_unsafe_options=False.

Proof of Concept

from git import TagReference
t = TagReference.create(repo, "vpwn", reference="--file=/home/app/.ssh/id_rsa")
print(t.tag.message)   # contents of the file

Attack Chain

  1. Entry: app calls TagReference.create(repo, name, reference=<user>) with reference="--file=/home/app/.ssh/id_rsa".
  2. Check: Git.check_unsafe_options(_option_candidates([], kwargs), ["--file","-F"]) @ tag.py:137-141. Guard: denylist includes --file/-F. Bypass proof: _option_candidates receives args=[] → the positional reference is never a candidate (the kwarg spelling file="…" IS blocked; only the positional escapes).
  3. Sink: repo.git.tag(*args, **kwargs) @ tag.py:158 → no --. argv (observed): ['git','tag','-f','vpwn','--file=<secret>'].
  4. Impact: annotated tag created; tagref.tag.message == file contents (arbitrary file read).

Bypass Evidence

Independently reproduced (independent test harness, git 2.43.0, default allow_unsafe_options=False): TagReference.create(repo,'vp','--file=<secret>') → PASSED; tag.message == 'GATE_SECRET_LINE_A\nGATE_SECRET_LINE_B'. Control: TagReference.create(..., file='<secret>')UnsafeOptionError: --file is not allowed. Fix-commit read: 3af0c251 adds _option_candidates([], kwargs) (empty args → positional never a candidate).

Affected Versions

GitPython <= 3.1.58 (sink present verbatim on the latest release tag; git diff 3.1.57..HEAD touches only test files).

Suggested Fix

Include the positional reference (and path) in the option-candidate list passed to check_unsafe_options, or place a -- separator before the positional arguments in TagReference.create().


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

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.58"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "GitPython"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.59"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-78679"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-88",
      "CWE-200"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-09-08T19:42:22Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "## Summary\n`TagReference.create()` forwards a caller-influenced positional `reference` value into `git tag` without it ever being inspected by the unsafe-option guard, allowing an arbitrary file read (the file\u0027s contents are returned in-band as the annotated tag message). This is an incomplete-fix bypass of commit `3af0c251` (the fix for GHSA-3f7w-8rr8-f37f\u0027s tag instance).\n\n## Root Cause\nThe fix `3af0c251` added `unsafe_git_tag_options = [\"--file\",\"-F\"]` and a guard call, but the guard is `Git.check_unsafe_options(options=Git._option_candidates([], kwargs), unsafe_options=...)` at `git/refs/tag.py:139` \u2014 it passes an EMPTY args list and inspects **kwargs only**. The dangerous values `path` and `reference` are POSITIONALS (`args = (path, reference)`, tag.py:156), placed before any `--`. A user-influenced `reference=\"--file=\u003cpath\u003e\"` therefore reaches `git tag` as the exact `--file` option the fix intended to block, creating an annotated tag whose message is the file\u0027s contents.\n\n## Impact\nArbitrary local file read at the privileges of the host process; contents returned in-band via `tagref.tag.message`. Requires the embedding application to forward a caller-influenced `reference` value into `TagReference.create()` (pure VALUE control \u2014 the CVE-2026-42215 threat model). Default `allow_unsafe_options=False`.\n\n## Proof of Concept\n```python\nfrom git import TagReference\nt = TagReference.create(repo, \"vpwn\", reference=\"--file=/home/app/.ssh/id_rsa\")\nprint(t.tag.message)   # contents of the file\n```\n\n## Attack Chain\n1. Entry: app calls `TagReference.create(repo, name, reference=\u003cuser\u003e)` with `reference=\"--file=/home/app/.ssh/id_rsa\"`.\n2. Check: `Git.check_unsafe_options(_option_candidates([], kwargs), [\"--file\",\"-F\"])` @ tag.py:137-141. Guard: denylist includes `--file`/`-F`. Bypass proof: `_option_candidates` receives `args=[]` \u2192 the positional `reference` is never a candidate (the kwarg spelling `file=\"\u2026\"` IS blocked; only the positional escapes).\n3. Sink: `repo.git.tag(*args, **kwargs)` @ tag.py:158 \u2192 no `--`. argv (observed): `[\u0027git\u0027,\u0027tag\u0027,\u0027-f\u0027,\u0027vpwn\u0027,\u0027--file=\u003csecret\u003e\u0027]`.\n4. Impact: annotated tag created; `tagref.tag.message` == file contents (arbitrary file read).\n\n## Bypass Evidence\nIndependently reproduced (independent test harness, git 2.43.0, default `allow_unsafe_options=False`): `TagReference.create(repo,\u0027vp\u0027,\u0027--file=\u003csecret\u003e\u0027)` \u2192 PASSED; `tag.message == \u0027GATE_SECRET_LINE_A\\nGATE_SECRET_LINE_B\u0027`. Control: `TagReference.create(..., file=\u0027\u003csecret\u003e\u0027)` \u2192 `UnsafeOptionError: --file is not allowed`. Fix-commit read: `3af0c251` adds `_option_candidates([], kwargs)` (empty args \u2192 positional never a candidate).\n\n## Affected Versions\n`GitPython \u003c= 3.1.58` (sink present verbatim on the latest release tag; `git diff 3.1.57..HEAD` touches only test files).\n\n## Suggested Fix\nInclude the positional `reference` (and `path`) in the option-candidate list passed to `check_unsafe_options`, or place a `--` separator before the positional arguments in `TagReference.create()`.\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
  "id": "GHSA-3wxw-xv34-2frg",
  "modified": "2026-09-08T19:42:22Z",
  "published": "2026-09-08T19:42:22Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-3wxw-xv34-2frg"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-78679"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/pull/2208"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/commit/1b0d2d9b91575f7db44ef4ff58ac37fc9335e5f6"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gitpython-developers/GitPython"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.59"
    },
    {
      "type": "WEB",
      "url": "https://www.vulncheck.com/advisories/gitpython-before-arbitrary-file-read-via-tagreference-create"
    }
  ],
  "schema_version": "1.4.0",
  "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: TagReference.create positional reference bypasses kwargs-only --file guard, enabling arbitrary file read (incomplete fix of 3af0c251)"
}



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…