GHSA-9RJ7-RF2P-W77R

Vulnerability from github – Published: 2026-08-07 15:36 – Updated: 2026-08-07 15:36
VLAI
Summary
GitPython: Unguarded git option forwarding in Repo.init enables arbitrary command execution via --template clone hooks
Details

Summary

Repo.init() forwards **kwargs verbatim to git init with no unsafe-option guard and no allow_unsafe_options parameter. git init --template=<dir> copies <dir>/hooks/* into the new repo's .git/hooks, so an attacker-controlled template kwarg plants a hook that executes on the next git operation → arbitrary code execution. --template is already recognized as unsafe for clone (it is on unsafe_git_clone_options, and GHSA-6p8h-3wgx-97gf covers the clone path), but Repo.init is a distinct method that never received a guard and needs an independent fix.

Root Cause

Repo.init(path, mkdir, odbt, expand_vars, **kwargs) is a bare git.init(**kwargs) (git/repo/base.py:1435) with no check_unsafe_options and no allow_unsafe_options.

Impact

Arbitrary code execution (hook fires on next git op) at the privileges of the host process. Two preconditions raise attack complexity (AC:H): the app must forward a template= kwarg (KEY control) AND the attacker must stage an executable hook directory at a known path — the same profile GHSA-6p8h-3wgx-97gf accepted as HIGH for the clone path. Default allow_unsafe_options is irrelevant here because Repo.init has no guard at all.

Proof of Concept

# attacker stages /evil/hooks/post-commit (executable)
from git import Repo
Repo.init(path, template="/evil")
# next commit runs /evil/hooks/post-commit -> ACE

Attack Chain

  1. Entry: attacker stages /evil/hooks/post-commit (executable) and gets the app to call Repo.init(path, template='/evil').
  2. Check: NONE on Repo.init. Bypass proof: base.py:1435 is a bare git.init(**kwargs). argv (observed): ['git','init','--template=/evil'].
  3. Sink: git copies /evil/hooks/post-commit<repo>/.git/hooks/post-commit.
  4. Impact: next commit runs the hook → arbitrary code execution.

Bypass Evidence

Independently reproduced (gate harness): Repo.init(dst, template='<evil>') → argv ['git','init','--template=<evil>'] unguarded; hook copied into .git/hooks/post-commit; after git commit the INIT_ACE marker was created. --separate-git-dir=<path> is a parallel arbitrary-redirect vector through the same unguarded sink (value control only).

Affected Versions

GitPython <= 3.1.57 (unguarded git.init(**kwargs) present verbatim on the latest release tag).

Suggested Fix

Add a check_unsafe_options guard (with an allow_unsafe_options parameter) to Repo.init, consulting a denylist that includes --template and --separate-git-dir (path-taking / hook-installing options).


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

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 3.1.57"
      },
      "package": {
        "ecosystem": "PyPI",
        "name": "GitPython"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "3.1.58"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-88",
      "CWE-94"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-08-07T15:36:43Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\n`Repo.init()` forwards `**kwargs` verbatim to `git init` with no unsafe-option guard and no `allow_unsafe_options` parameter. `git init --template=\u003cdir\u003e` copies `\u003cdir\u003e/hooks/*` into the new repo\u0027s `.git/hooks`, so an attacker-controlled `template` kwarg plants a hook that executes on the next git operation \u2192 arbitrary code execution. `--template` is already recognized as unsafe for clone (it is on `unsafe_git_clone_options`, and GHSA-6p8h-3wgx-97gf covers the clone path), but `Repo.init` is a distinct method that never received a guard and needs an independent fix.\n\n## Root Cause\n`Repo.init(path, mkdir, odbt, expand_vars, **kwargs)` is a bare `git.init(**kwargs)` (git/repo/base.py:1435) with no `check_unsafe_options` and no `allow_unsafe_options`.\n\n## Impact\nArbitrary code execution (hook fires on next git op) at the privileges of the host process. Two preconditions raise attack complexity (AC:H): the app must forward a `template=` kwarg (KEY control) AND the attacker must stage an executable hook directory at a known path \u2014 the same profile GHSA-6p8h-3wgx-97gf accepted as HIGH for the clone path. Default `allow_unsafe_options` is irrelevant here because `Repo.init` has no guard at all.\n\n## Proof of Concept\n```python\n# attacker stages /evil/hooks/post-commit (executable)\nfrom git import Repo\nRepo.init(path, template=\"/evil\")\n# next commit runs /evil/hooks/post-commit -\u003e ACE\n```\n\n## Attack Chain\n1. Entry: attacker stages `/evil/hooks/post-commit` (executable) and gets the app to call `Repo.init(path, template=\u0027/evil\u0027)`.\n2. Check: NONE on `Repo.init`. Bypass proof: base.py:1435 is a bare `git.init(**kwargs)`. argv (observed): `[\u0027git\u0027,\u0027init\u0027,\u0027--template=/evil\u0027]`.\n3. Sink: git copies `/evil/hooks/post-commit` \u2192 `\u003crepo\u003e/.git/hooks/post-commit`.\n4. Impact: next commit runs the hook \u2192 arbitrary code execution.\n\n## Bypass Evidence\nIndependently reproduced (gate harness): `Repo.init(dst, template=\u0027\u003cevil\u003e\u0027)` \u2192 argv `[\u0027git\u0027,\u0027init\u0027,\u0027--template=\u003cevil\u003e\u0027]` unguarded; hook copied into `.git/hooks/post-commit`; after `git commit` the `INIT_ACE` marker was created. `--separate-git-dir=\u003cpath\u003e` is a parallel arbitrary-redirect vector through the same unguarded sink (value control only).\n\n## Affected Versions\n`GitPython \u003c= 3.1.57` (unguarded `git.init(**kwargs)` present verbatim on the latest release tag).\n\n## Suggested Fix\nAdd a `check_unsafe_options` guard (with an `allow_unsafe_options` parameter) to `Repo.init`, consulting a denylist that includes `--template` and `--separate-git-dir` (path-taking / hook-installing options).\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
  "id": "GHSA-9rj7-rf2p-w77r",
  "modified": "2026-08-07T15:36:43Z",
  "published": "2026-08-07T15:36:43Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-9rj7-rf2p-w77r"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/pull/2204"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/commit/d9ddb55bdc66"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/gitpython-developers/GitPython"
    },
    {
      "type": "WEB",
      "url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "GitPython: Unguarded git option forwarding in Repo.init enables arbitrary command execution via --template clone hooks"
}



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…